mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 01:03:01 +08:00
Compare commits
79
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
8ad86928f1 | ||
|
|
d8e5936d45 | ||
|
|
caa717544b | ||
|
|
66b77f8d6d | ||
|
|
ab5264c8ca | ||
|
|
db12309bae | ||
|
|
2953e0dcc5 | ||
|
|
1b18ac9fe3 | ||
|
|
2cdad6622a | ||
|
|
7601d11a7a | ||
|
|
0bb4c6ef09 | ||
|
|
449624fd72 | ||
|
|
99f4f46062 | ||
|
|
b08d11c069 | ||
|
|
ac41e838ec | ||
|
|
836ddf3bbc | ||
|
|
0e815c626c | ||
|
|
4b7d035e9e | ||
|
|
cb3c44f48d | ||
|
|
8b3b9d33de | ||
|
|
59d99509c8 | ||
|
|
2933b76237 | ||
|
|
36c6d8974e | ||
|
|
d6cab76728 | ||
|
|
b4f740f338 | ||
|
|
a004de25ac | ||
|
|
d0584413ca | ||
|
|
ba57537108 | ||
|
|
6dcb110737 | ||
|
|
b599a832da | ||
|
|
9cbd5dd778 | ||
|
|
ed31c51237 | ||
|
|
e01707d25e | ||
|
|
73f118fe1c | ||
|
|
0234918f30 | ||
|
|
c90ba7e061 | ||
|
|
2aa4219838 | ||
|
|
b98417ab39 | ||
|
|
ead58dd606 | ||
|
|
a93e935585 | ||
|
|
aeb07e880e | ||
|
|
e0ce1f309e | ||
|
|
d74c81c3d1 | ||
|
|
2fa7200fb3 | ||
|
|
9f7d15e1b1 | ||
|
|
fdf00f1b18 | ||
|
|
abe7bbda15 | ||
|
|
795139d78f | ||
|
|
c7390ef0d7 | ||
|
|
8a926a3db3 | ||
|
|
7d5e49b44c | ||
|
|
555c3eac7a |
@@ -1,9 +1,3 @@
|
||||
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
||||
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
||||
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
|
||||
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
||||
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
||||
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
|
||||
* [Server] Add support for interception of will messages (#1613).
|
||||
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
|
||||
* [Server] Fixed not working _UpdateRetainedMessageAsync_ public api (#1858, thanks to @kimdiego2098).
|
||||
* [Client] Added support for custom CA chain validation (#1851, thanks to @rido-min).
|
||||
* [Client] Fixed handling of unobserved tasks exceptions (#1871).
|
||||
@@ -3,7 +3,7 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "4.1.4.${{github.run_number}}"
|
||||
VERSION: "4.3.2.${{github.run_number}}"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
dotnet-version: |
|
||||
3.1.x
|
||||
6.0.x
|
||||
7.0.x
|
||||
|
||||
- name: Setup MSBuild
|
||||
uses: microsoft/setup-msbuild@v1.1
|
||||
@@ -53,7 +54,7 @@ jobs:
|
||||
uses: darenm/Setup-VSTest@v1
|
||||
|
||||
- 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
|
||||
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_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/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/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -50,6 +50,22 @@ MQTTnet is a high performance .NET library for MQTT based communication. It prov
|
||||
* A custom message interceptor can be added which allows transforming or extending every received application message
|
||||
* Validate subscriptions and deny subscribing of certain topics depending on requesting clients
|
||||
|
||||
## Getting Started
|
||||
|
||||
MQTTnet is delivered via <b>NuGet</b> package manager. You can find the packages here: https://www.nuget.org/packages/MQTTnet/
|
||||
|
||||
Use these command in the Package Manager console to install MQTTnet manually:
|
||||
```
|
||||
Install-Package MQTTnet
|
||||
```
|
||||
|
||||
Samples for using MQTTnet are part of this repository. For starters these samples are recommended:
|
||||
|
||||
- [Connect with a broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Connection_Samples.cs)
|
||||
- [Subscribing to data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Subscribe_Samples.cs)
|
||||
- [Publishing data](https://github.com/dotnet/MQTTnet/blob/master/Samples/Client/Client_Publish_Samples.cs)
|
||||
- [Host own broker](https://github.com/dotnet/MQTTnet/blob/master/Samples/Server/Server_Simple_Samples.cs)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions.WebSocket4Net;
|
||||
using MQTTnet.Formatter;
|
||||
@@ -16,6 +17,28 @@ namespace MQTTnet.Samples.Client;
|
||||
|
||||
public static class Client_Connection_Samples
|
||||
{
|
||||
public static async Task Clean_Disconnect()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
|
||||
* to the server and close the connection afterwards.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will send the DISCONNECT packet. Calling _Dispose_ without DisconnectAsync the
|
||||
// connection is closed in a "not clean" way. See MQTT specification for more details.
|
||||
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client()
|
||||
{
|
||||
/*
|
||||
@@ -113,15 +136,15 @@ public static class Client_Connection_Samples
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTls(
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// 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.
|
||||
o.SslProtocol = SslProtocols.Tls12;
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
@@ -148,7 +171,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
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);
|
||||
|
||||
@@ -170,7 +193,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
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);
|
||||
|
||||
@@ -193,13 +216,11 @@ public static class Client_Connection_Samples
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithTls(
|
||||
o =>
|
||||
{
|
||||
.WithTlsOptions(
|
||||
o => o.WithCertificateValidationHandler(
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.CertificateValidationHandler = _ => true;
|
||||
})
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
@@ -214,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()
|
||||
{
|
||||
/*
|
||||
@@ -232,7 +278,7 @@ public static class Client_Connection_Samples
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,18 +315,19 @@ public static class Client_Connection_Samples
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTls(
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
o.CertificateValidationHandler = eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
};
|
||||
o.WithCertificateValidationHandler(
|
||||
eventArgs =>
|
||||
{
|
||||
eventArgs.Certificate.Subject.DumpToConsole();
|
||||
eventArgs.Certificate.GetExpirationDateString().DumpToConsole();
|
||||
eventArgs.Chain.ChainPolicy.RevocationMode.DumpToConsole();
|
||||
eventArgs.Chain.ChainStatus.DumpToConsole();
|
||||
eventArgs.SslPolicyErrors.DumpToConsole();
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.Build();
|
||||
|
||||
@@ -297,7 +344,7 @@ public static class Client_Connection_Samples
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in METTv5.0.0+.
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
@@ -384,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 UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable UnusedMember.Local
|
||||
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Packets;
|
||||
@@ -176,4 +177,62 @@ public static class Client_Subscribe_Samples
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public static class PackageInspection_Samples
|
||||
.WithTcpServer("broker.hivemq.com")
|
||||
.Build();
|
||||
|
||||
mqttClient.InspectPackage += OnInspectPackage;
|
||||
mqttClient.InspectPacketAsync += OnInspectPacket;
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
@@ -39,7 +39,7 @@ public static class PackageInspection_Samples
|
||||
}
|
||||
}
|
||||
|
||||
static Task OnInspectPackage(InspectMqttPacketEventArgs eventArgs)
|
||||
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Direction == MqttPacketFlowDirection.Inbound)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
+18
-1
@@ -4,7 +4,13 @@
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
Console.WriteLine("Welcome to MQTTnet samples!");
|
||||
Console.BackgroundColor = ConsoleColor.White;
|
||||
Console.BackgroundColor = ConsoleColor.Red;
|
||||
Console.Title = "Samples - MQTTnet";
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.WriteLine("- Welcome to MQTTnet samples! -");
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
var sampleClasses = Assembly.GetExecutingAssembly().GetExportedTypes().OrderBy(c => c.Name).ToList();
|
||||
@@ -16,7 +22,11 @@ foreach (var sampleClass in sampleClasses)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample class (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
var input = Console.ReadLine();
|
||||
var selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleClass = sampleClasses[selectedIndex];
|
||||
@@ -29,12 +39,19 @@ foreach (var sampleMethod in sampleMethods)
|
||||
index++;
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("Please choose sample (press Enter to continue): ");
|
||||
Console.ResetColor();
|
||||
|
||||
input = Console.ReadLine();
|
||||
selectedIndex = int.Parse(input ?? "0");
|
||||
var selectedSampleMethod = sampleMethods[selectedIndex];
|
||||
|
||||
Console.WriteLine();
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.WriteLine("Executing sample...");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine();
|
||||
|
||||
try
|
||||
|
||||
@@ -17,7 +17,7 @@ using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_Asp_Net_Samples
|
||||
public static class Server_ASP_NET_Samples
|
||||
{
|
||||
public static Task Start_Server_With_WebSockets_Support()
|
||||
{
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
// ReSharper disable EmptyConstructor
|
||||
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||
|
||||
public static class Server_Diagnostics_Samples
|
||||
{
|
||||
public static async Task Get_Notified_When_Client_Received_Message()
|
||||
{
|
||||
/*
|
||||
* This sample starts a MQTT server and attaches an event which gets fired whenever
|
||||
* a client received and acknowledged a PUBLISH packet.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
// Attach the event handler.
|
||||
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
|
||||
{
|
||||
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
|
||||
|
||||
// It is also possible to read additional data from the client response. This requires casting the response packet.
|
||||
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
|
||||
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
|
||||
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
|
||||
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Text.Json;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
@@ -32,7 +34,10 @@ public static class Server_Retained_Messages_Samples
|
||||
{
|
||||
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.");
|
||||
}
|
||||
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
|
||||
// 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);
|
||||
Console.WriteLine("Retained messages saved.");
|
||||
}
|
||||
@@ -79,4 +86,59 @@ public static class Server_Retained_Messages_Samples
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// 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.
|
||||
|
||||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Samples.Server;
|
||||
|
||||
public static class Server_TLS_Samples
|
||||
{
|
||||
public static async Task Run_Server_With_Self_Signed_Certificate()
|
||||
{
|
||||
/*
|
||||
* This sample starts a simple MQTT server which will accept any TCP connection.
|
||||
* It also has an encrypted connection using a self signed TLS certificate.
|
||||
*
|
||||
* See sample "Run_Minimal_Server" for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttFactory();
|
||||
|
||||
// This certificate is self signed so that
|
||||
var certificate = CreateSelfSignedCertificate("1.3.6.1.5.5.7.3.1");
|
||||
|
||||
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
|
||||
|
||||
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||
{
|
||||
await mqttServer.StartAsync();
|
||||
|
||||
Console.WriteLine("Press Enter to exit.");
|
||||
Console.ReadLine();
|
||||
|
||||
// Stop and dispose the MQTT server if it is no longer needed!
|
||||
await mqttServer.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
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) }, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
connection.Transport = pipe;
|
||||
var ctx = new MqttConnectionContext(serializer, connection);
|
||||
|
||||
await ctx.SendPacketAsync(new MqttPublishPacket { Payload = new byte[20_000] }, CancellationToken.None).ConfigureAwait(false);
|
||||
await ctx.SendPacketAsync(new MqttPublishPacket { PayloadSegment = new ArraySegment<byte>(new byte[20_000]) }, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var readResult = await pipe.Send.Reader.ReadAsync();
|
||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace MQTTnet.AspNetCore.Tests
|
||||
{
|
||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", Payload = new byte[5]}).Join();
|
||||
var buffer = serializer.Encode(new MqttPublishPacket {Topic = "a", PayloadSegment = new byte[5]}).Join();
|
||||
|
||||
var sequence = new ReadOnlySequence<byte>(buffer.Array, buffer.Offset, buffer.Count);
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
_sender = new SocketSender(_socket, PipeScheduler.ThreadPool);
|
||||
_receiver = new SocketReceiver(_socket, PipeScheduler.ThreadPool);
|
||||
}
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public override ValueTask DisposeAsync()
|
||||
#else
|
||||
public Task DisposeAsync()
|
||||
@@ -57,7 +57,7 @@ namespace MQTTnet.AspNetCore.Client.Tcp
|
||||
|
||||
_socket?.Dispose();
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
return base.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
|
||||
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
|
||||
@@ -51,11 +51,11 @@
|
||||
<DefineConstants>RELEASE;NETSTANDARD2_0</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' ">
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' ">
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp3.1' and '$(TargetFramework)' != 'net5.0' and '$(TargetFramework)' != 'net6.0' and '$(TargetFramework)' != 'net7.0' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -33,8 +33,11 @@ namespace MQTTnet.AspNetCore
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
if (!(_connection is TcpConnection tcp) || tcp.IsConnected)
|
||||
{
|
||||
_input = connection.Transport.Input;
|
||||
_output = connection.Transport.Output;
|
||||
}
|
||||
}
|
||||
|
||||
public long BytesReceived { get; private set; }
|
||||
@@ -211,8 +214,17 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
// zero copy
|
||||
// https://github.com/dotnet/runtime/blob/e31ddfdc4f574b26231233dc10c9a9c402f40590/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeWriter.cs#L279
|
||||
await _output.WriteAsync(buffer.Packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(_output, buffer);
|
||||
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
BytesSent += buffer.Length;
|
||||
}
|
||||
|
||||
@@ -2,38 +2,34 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
using System;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Implementations;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
{
|
||||
IMqttNetLogger _logger = new MqttNetNullLogger();
|
||||
|
||||
IMqttNetLogger _logger = MqttNetNullLogger.Instance;
|
||||
|
||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
public void Dispose()
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||
{
|
||||
if (webSocket == null) throw new ArgumentNullException(nameof(webSocket));
|
||||
if (webSocket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
|
||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||
|
||||
@@ -48,7 +44,7 @@ namespace MQTTnet.AspNetCore
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
||||
|
||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, null, _logger))
|
||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||
{
|
||||
await clientHandler(channelAdapter).ConfigureAwait(false);
|
||||
}
|
||||
@@ -60,8 +56,15 @@ namespace MQTTnet.AspNetCore
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
var channel = new MemoryMqttChannel(_stream);
|
||||
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
|
||||
}
|
||||
|
||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
@@ -26,6 +26,10 @@
|
||||
<PackageReference Include="System.IO.Pipelines" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="System.IO.Pipelines" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||
|
||||
@@ -30,17 +30,17 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
}
|
||||
@@ -83,11 +83,11 @@ namespace MQTTnet.Benchmarks
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574589);
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(_demoPayload);
|
||||
writer.WriteBinary(_demoPayload);
|
||||
|
||||
writer.Reset(0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Formatter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipelines;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
[SimpleJob(RuntimeMoniker.Net60)]
|
||||
[RPlotExporter, RankColumn]
|
||||
[MemoryDiagnoser]
|
||||
public class SendPacketAsyncBenchmark : BaseBenchmark
|
||||
{
|
||||
MemoryStream stream;
|
||||
MqttPacketBuffer buffer;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
stream = new MemoryStream(1024);
|
||||
var packet = new ArraySegment<byte>(new byte[10]);
|
||||
buffer = new MqttPacketBuffer(packet);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public async ValueTask Before()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public async ValueTask After()
|
||||
{
|
||||
stream.Position = 0;
|
||||
var output = PipeWriter.Create(stream);
|
||||
|
||||
if (buffer.Payload.Count == 0)
|
||||
{
|
||||
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePacketBuffer(output, buffer);
|
||||
await output.FlushAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||
{
|
||||
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||
|
||||
var span = output.GetSpan(buffer.Length);
|
||||
|
||||
buffer.Packet.AsSpan().CopyTo(span);
|
||||
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||
|
||||
output.Advance(buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ namespace MQTTnet.Benchmarks
|
||||
public void Deserialize_10000_Messages()
|
||||
{
|
||||
var channel = new BenchmarkMqttChannel(_serializedPacket);
|
||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
||||
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.ManagedClient</AssemblyName>
|
||||
|
||||
@@ -353,13 +353,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
return remainingTime < TimeSpan.Zero ? TimeSpan.Zero : remainingTime;
|
||||
}
|
||||
|
||||
async Task HandleSubscriptionExceptionAsync(Exception exception)
|
||||
async Task HandleSubscriptionExceptionAsync(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||
{
|
||||
_logger.Warning(exception, "Synchronizing subscriptions failed.");
|
||||
|
||||
if (_synchronizingSubscriptionsFailedEvent.HasHandlers)
|
||||
{
|
||||
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception)).ConfigureAwait(false);
|
||||
await _synchronizingSubscriptionsFailedEvent.InvokeAsync(new ManagedProcessFailedEventArgs(exception, addedSubscriptions, removedSubscriptions)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,11 +457,13 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
_logger.Info("Publishing subscriptions at reconnect");
|
||||
|
||||
List<MqttTopicFilter> topicFilters = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (_reconnectSubscriptions.Any())
|
||||
{
|
||||
var topicFilters = new List<MqttTopicFilter>();
|
||||
topicFilters = new List<MqttTopicFilter>();
|
||||
|
||||
foreach (var sub in _reconnectSubscriptions)
|
||||
{
|
||||
@@ -479,7 +481,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
||||
await HandleSubscriptionExceptionAsync(exception, topicFilters, null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,6 +593,9 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
|
||||
await InternalClient.UnsubscribeAsync(unsubscribeOptionsBuilder.Build()).ConfigureAwait(false);
|
||||
|
||||
//clear because these worked, maybe the subscribe below will fail, only report those
|
||||
removedSubscriptions.Clear();
|
||||
}
|
||||
|
||||
if (addedSubscriptions != null && addedSubscriptions.Any())
|
||||
@@ -607,7 +612,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleSubscriptionExceptionAsync(exception).ConfigureAwait(false);
|
||||
await HandleSubscriptionExceptionAsync(exception, addedSubscriptions, removedSubscriptions).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
|
||||
/// <summary>
|
||||
/// Defines the maximum amount of topic filters which will be sent in a SUBSCRIBE/UNSUBSCRIBE packet.
|
||||
/// Amazon AWS limits this number to 8. The default is int.MaxValue.
|
||||
/// Amazon Web Services (AWS) limits this number to 8. The default is int.MaxValue.
|
||||
/// </summary>
|
||||
public int MaxTopicFiltersInSubscribeUnsubscribePackets { get; set; } = int.MaxValue;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,33 @@
|
||||
// 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.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public class ManagedProcessFailedEventArgs : EventArgs
|
||||
{
|
||||
public ManagedProcessFailedEventArgs(Exception exception)
|
||||
public ManagedProcessFailedEventArgs(Exception exception, List<MqttTopicFilter> addedSubscriptions, List<string> removedSubscriptions)
|
||||
{
|
||||
Exception = exception ?? throw new ArgumentNullException(nameof(exception));
|
||||
|
||||
if (addedSubscriptions != null)
|
||||
{
|
||||
AddedSubscriptions = new List<string>(addedSubscriptions.Select(item => item.Topic));
|
||||
}
|
||||
|
||||
if (removedSubscriptions != null)
|
||||
{
|
||||
RemovedSubscriptions = new List<string>(removedSubscriptions);
|
||||
}
|
||||
}
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
public List<string> AddedSubscriptions { get; }
|
||||
public List<string> RemovedSubscriptions { get; }
|
||||
}
|
||||
}
|
||||
@@ -12,22 +12,37 @@ namespace MQTTnet.Extensions.ManagedClient
|
||||
{
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttClient mqttClient = null)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (mqttClient == null)
|
||||
{
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(), factory.DefaultLogger);
|
||||
}
|
||||
|
||||
|
||||
return new ManagedMqttClient(mqttClient, factory.DefaultLogger);
|
||||
}
|
||||
|
||||
|
||||
public static IManagedMqttClient CreateManagedMqttClient(this MqttFactory factory, IMqttNetLogger logger)
|
||||
{
|
||||
if (factory == null) throw new ArgumentNullException(nameof(factory));
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
if (factory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
return new ManagedMqttClient(factory.CreateMqttClient(logger), logger);
|
||||
}
|
||||
|
||||
public static ManagedMqttClientOptionsBuilder CreateManagedMqttClientOptionsBuilder(this MqttFactory _)
|
||||
{
|
||||
return new ManagedMqttClientOptionsBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -7,8 +8,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -17,9 +20,9 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
readonly IMqttClient _mqttClient;
|
||||
readonly MqttRpcClientOptions _options;
|
||||
|
||||
|
||||
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||
|
||||
|
||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||
{
|
||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
@@ -40,13 +43,13 @@ namespace MQTTnet.Extensions.Rpc
|
||||
_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))
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -60,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)
|
||||
{
|
||||
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 requestTopic = topicNames.RequestTopic;
|
||||
@@ -83,11 +86,14 @@ namespace MQTTnet.Extensions.Rpc
|
||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||
}
|
||||
|
||||
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
|
||||
.WithPayload(payload)
|
||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||
.WithResponseTopic(responseTopic)
|
||||
.Build();
|
||||
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
|
||||
|
||||
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
requestMessageBuilder.WithResponseTopic(responseTopic);
|
||||
}
|
||||
|
||||
var requestMessage = requestMessageBuilder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -115,7 +121,6 @@ namespace MQTTnet.Extensions.Rpc
|
||||
finally
|
||||
{
|
||||
_waitingCalls.TryRemove(responseTopic, out _);
|
||||
|
||||
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +132,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||
var payloadBuffer = eventArgs.ApplicationMessage.PayloadSegment.ToArray();
|
||||
awaitable.TrySetResult(payloadBuffer);
|
||||
|
||||
// Set this message to handled to that other code can avoid execution etc.
|
||||
eventArgs.IsHandled = true;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -11,13 +12,13 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
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));
|
||||
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -10,9 +11,11 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
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));
|
||||
Parameters = parameters;
|
||||
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
@@ -20,6 +23,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
|
||||
public string MethodName { get; }
|
||||
|
||||
public IDictionary<string, object> Parameters { get; }
|
||||
|
||||
public IMqttClient MqttClient { get; }
|
||||
|
||||
public MqttRpcClientOptions Options { get; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461;net48</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
|
||||
|
||||
<AssemblyName>MQTTnet.Extensions.WebSocket4Net</AssemblyName>
|
||||
|
||||
@@ -47,13 +47,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
var uri = _webSocketOptions.Uri;
|
||||
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
|
||||
{
|
||||
uri = "wss://" + uri;
|
||||
uri = "ws://" + uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,18 +92,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
var webSocketVersion = WebSocketVersion.None;
|
||||
var receiveBufferSize = 0;
|
||||
|
||||
var certificates = new X509CertificateCollection();
|
||||
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
|
||||
}
|
||||
}
|
||||
var certificates = _webSocketOptions.TlsOptions?.ClientCertificatesProvider?.GetCertificates();
|
||||
|
||||
_webSocket = new WebSocket(uri, subProtocol, cookies, customHeaders, userAgent, origin, webSocketVersion, proxy, sslProtocols, receiveBufferSize)
|
||||
{
|
||||
|
||||
@@ -27,8 +27,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return new MqttChannelAdapter(
|
||||
new MqttTcpChannel(options),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
logger)
|
||||
{
|
||||
PacketInspector = packetInspector
|
||||
};
|
||||
}
|
||||
|
||||
case MqttClientWebSocketOptions webSocketOptions:
|
||||
@@ -36,8 +38,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
||||
return new MqttChannelAdapter(
|
||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||
packetInspector,
|
||||
logger);
|
||||
logger)
|
||||
{
|
||||
PacketInspector = packetInspector
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -33,9 +33,18 @@ namespace MQTTnet.TestApp
|
||||
|
||||
client.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
var payloadText = string.Empty;
|
||||
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||
{
|
||||
payloadText = Encoding.UTF8.GetString(
|
||||
e.ApplicationMessage.PayloadSegment.Array,
|
||||
e.ApplicationMessage.PayloadSegment.Offset,
|
||||
e.ApplicationMessage.PayloadSegment.Count);
|
||||
}
|
||||
|
||||
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
|
||||
Console.WriteLine($"+ Topic = {e.ApplicationMessage.Topic}");
|
||||
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}");
|
||||
Console.WriteLine($"+ Payload = {payloadText}");
|
||||
Console.WriteLine($"+ QoS = {e.ApplicationMessage.QualityOfServiceLevel}");
|
||||
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
|
||||
Console.WriteLine();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<DebugType>Full</DebugType>
|
||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace MQTTnet.TestApp
|
||||
return new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes(Payload),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(Payload)),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||
};
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce
|
||||
};
|
||||
|
||||
@@ -280,7 +280,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce
|
||||
};
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace MQTTnet.TestApp
|
||||
var message = new MqttApplicationMessage
|
||||
{
|
||||
Topic = "A/B/C",
|
||||
Payload = Encoding.UTF8.GetBytes("Hello World"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello World")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce
|
||||
};
|
||||
|
||||
|
||||
@@ -20,21 +20,25 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
// TLS13 is only available in Net5.0
|
||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
||||
var unsafeTls13 = new MqttClientTlsOptions
|
||||
{
|
||||
UseTls = true,
|
||||
SslProtocol = SslProtocols.Tls13,
|
||||
// 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
|
||||
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
||||
var unsafeTls12 = new MqttClientTlsOptions
|
||||
{
|
||||
UseTls = true,
|
||||
SslProtocol = SslProtocols.Tls12,
|
||||
// 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
|
||||
@@ -44,16 +48,16 @@ namespace MQTTnet.TestApp
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||
|
||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("mqtt.eclipseprojects.io:443/mqtt"))
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build(),
|
||||
true);
|
||||
#endif
|
||||
|
||||
@@ -68,12 +72,12 @@ namespace MQTTnet.TestApp
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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
|
||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||
#endif
|
||||
|
||||
await ExecuteTestAsync(
|
||||
@@ -81,26 +85,26 @@ namespace MQTTnet.TestApp
|
||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||
.WithCredentials("rw", "readwrite")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||
.WithTls(unsafeTls12)
|
||||
.WithTlsOptions(unsafeTls12)
|
||||
.Build());
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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(
|
||||
"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);
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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(
|
||||
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||
// true);
|
||||
await ExecuteTestAsync(
|
||||
"test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||
new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("test.mosquitto.org:8081/mqtt")).WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls12).Build(),
|
||||
true);
|
||||
|
||||
// broker.emqx.io
|
||||
await ExecuteTestAsync(
|
||||
@@ -109,30 +113,30 @@ namespace MQTTnet.TestApp
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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
|
||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTlsOptions(unsafeTls13).Build());
|
||||
#endif
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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(
|
||||
"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);
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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(
|
||||
"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);
|
||||
|
||||
// broker.hivemq.com
|
||||
@@ -142,15 +146,14 @@ namespace MQTTnet.TestApp
|
||||
|
||||
await ExecuteTestAsync(
|
||||
"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(
|
||||
"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);
|
||||
|
||||
// mqtt.swifitch.cz: Does not seem to operate any more
|
||||
|
||||
// cloudmqtt.com: Cannot test because it does not offer a free plan any more.
|
||||
|
||||
Write("Finished.", ConsoleColor.White);
|
||||
@@ -197,9 +200,10 @@ namespace MQTTnet.TestApp
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
@@ -100,7 +101,7 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
// Replace the payload with the timestamp. But also extending a JSON
|
||||
// based payload with the timestamp is a suitable use case.
|
||||
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(DateTime.Now.ToString("O"));
|
||||
e.ApplicationMessage.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes(DateTime.Now.ToString("O")));
|
||||
}
|
||||
|
||||
if (e.ApplicationMessage.Topic == "not_allowed_topic")
|
||||
@@ -143,10 +144,16 @@ namespace MQTTnet.TestApp
|
||||
|
||||
mqttServer.InterceptingPublishAsync += e =>
|
||||
{
|
||||
MqttNetConsoleLogger.PrintToConsole(
|
||||
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
|
||||
ConsoleColor.Magenta);
|
||||
var payloadText = string.Empty;
|
||||
if (e.ApplicationMessage.PayloadSegment.Count > 0)
|
||||
{
|
||||
payloadText = Encoding.UTF8.GetString(
|
||||
e.ApplicationMessage.PayloadSegment.Array,
|
||||
e.ApplicationMessage.PayloadSegment.Offset,
|
||||
e.ApplicationMessage.PayloadSegment.Count);
|
||||
}
|
||||
|
||||
MqttNetConsoleLogger.PrintToConsole($"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{payloadText}'", ConsoleColor.Magenta);
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ using MQTTnet.LowLevelClient;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.LowLevelMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class LowLevelMqttClient_Tests : BaseTestClass
|
||||
+105
-73
@@ -17,72 +17,11 @@ using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.ManagedMqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||
{
|
||||
[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]
|
||||
public async Task Connect_To_Invalid_Server()
|
||||
{
|
||||
@@ -149,6 +88,38 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
[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 ManagedClients_Will_Message_Send()
|
||||
{
|
||||
@@ -164,7 +135,7 @@ namespace MQTTnet.Tests.Client
|
||||
.Build();
|
||||
|
||||
var dyingClient = testEnvironment.CreateClient();
|
||||
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
var dyingManagedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
|
||||
|
||||
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).Build());
|
||||
|
||||
@@ -192,6 +163,67 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
[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]
|
||||
public async Task Start_Stop()
|
||||
{
|
||||
@@ -199,7 +231,7 @@ namespace MQTTnet.Tests.Client
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
@@ -226,7 +258,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort);
|
||||
var storage = new ManagedMqttClientTestStorage();
|
||||
|
||||
@@ -343,7 +375,7 @@ namespace MQTTnet.Tests.Client
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
|
||||
|
||||
var receivedManagedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
|
||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
|
||||
managedClient.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
@@ -371,7 +403,7 @@ namespace MQTTnet.Tests.Client
|
||||
// Make sure that it gets received after subscribing again.
|
||||
await managedClient.SubscribeAsync("topic");
|
||||
await LongTestDelay();
|
||||
|
||||
|
||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||
}
|
||||
}
|
||||
@@ -389,7 +421,7 @@ namespace MQTTnet.Tests.Client
|
||||
var receivingClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
|
||||
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;
|
||||
|
||||
@@ -422,7 +454,7 @@ namespace MQTTnet.Tests.Client
|
||||
//wait a bit for the subscription to become established
|
||||
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);
|
||||
|
||||
@@ -439,7 +471,7 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ManagedMqttClient> CreateManagedClientAsync(
|
||||
async Task<MQTTnet.Extensions.ManagedClient.ManagedMqttClient> CreateManagedClientAsync(
|
||||
TestEnvironment testEnvironment,
|
||||
IMqttClient underlyingClient = null,
|
||||
TimeSpan? connectionCheckInterval = null,
|
||||
@@ -456,7 +488,7 @@ namespace MQTTnet.Tests.Client
|
||||
// at connection check intervals
|
||||
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
|
||||
|
||||
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
var managedClient = new MQTTnet.Extensions.ManagedClient.ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
|
||||
|
||||
var connected = GetConnectedTask(managedClient);
|
||||
|
||||
@@ -473,7 +505,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="managedClient" />
|
||||
/// has connected
|
||||
/// </summary>
|
||||
Task GetConnectedTask(ManagedMqttClient managedClient)
|
||||
Task GetConnectedTask(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient)
|
||||
{
|
||||
var connected = new TaskCompletionSource<bool>();
|
||||
|
||||
@@ -495,7 +527,7 @@ namespace MQTTnet.Tests.Client
|
||||
/// name="expectedNumberOfMessages" />
|
||||
/// have been received
|
||||
/// </summary>
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(MQTTnet.Extensions.ManagedClient.ManagedMqttClient managedClient, int expectedNumberOfMessages)
|
||||
{
|
||||
var receivedMessages = new List<MqttApplicationMessage>();
|
||||
var result = new AsyncTaskCompletionSource<List<MqttApplicationMessage>>();
|
||||
@@ -0,0 +1,254 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Connection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_Custom_Reason()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_User_Properties()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.Factory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.IsNotNull(eventArgs.UserProperties);
|
||||
Assert.AreEqual(1, eventArgs.UserProperties.Count);
|
||||
Assert.AreEqual("test_name", eventArgs.UserProperties[0].Name);
|
||||
Assert.AreEqual("test_value", eventArgs.UserProperties[0].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[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]
|
||||
public async Task Return_Non_Success()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
server.ValidatingConnectionAsync += args =>
|
||||
{
|
||||
args.ResponseUserProperties = new List<MqttUserProperty>
|
||||
{
|
||||
new MqttUserProperty("Property", "Value")
|
||||
};
|
||||
|
||||
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().WithoutThrowOnNonSuccessfulConnectResponse().Build());
|
||||
|
||||
Assert.IsNotNull(response);
|
||||
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
||||
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-48
@@ -21,7 +21,7 @@ using MQTTnet.Tests.Mockups;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Tests : BaseTestClass
|
||||
@@ -71,60 +71,34 @@ namespace MQTTnet.Tests.Client
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
public async Task Connect_Disconnect_Connect()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(5)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(OperationCanceledException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.DisconnectAsync();
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttCommunicationException))]
|
||||
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public async Task Connect_Multiple_Times_Should_Fail()
|
||||
{
|
||||
var client = new MqttFactory().CreateMqttClient();
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithTimeout(TimeSpan.FromSeconds(2)).Build());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptions();
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
Assert.Fail("Must fail!");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Assert.IsNotNull(exception);
|
||||
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
|
||||
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
|
||||
}
|
||||
|
||||
await LongTestDelay(); // disconnected handler is called async
|
||||
Assert.IsTrue(disconnectHandlerCalled);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
await client.ConnectAsync(clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +296,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
Assert.IsNotNull(receivedMessage);
|
||||
Assert.AreEqual("A", receivedMessage.Topic);
|
||||
Assert.AreEqual(null, receivedMessage.Payload);
|
||||
Assert.AreEqual(null, receivedMessage.PayloadSegment.Array);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,7 +507,7 @@ namespace MQTTnet.Tests.Client
|
||||
|
||||
client2.ApplicationMessageReceivedAsync += e =>
|
||||
{
|
||||
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
|
||||
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment.ToArray()));
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
+1
-2
@@ -6,9 +6,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Extensions;
|
||||
|
||||
namespace MQTTnet.Tests.Client
|
||||
namespace MQTTnet.Tests.Clients
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttClientOptionsBuilder_Tests
|
||||
@@ -30,7 +30,7 @@ namespace MQTTnet.Tests.Diagnostics
|
||||
|
||||
var packets = new List<string>();
|
||||
|
||||
mqttClient.InspectPackage += eventArgs =>
|
||||
mqttClient.InspectPacketAsync += eventArgs =>
|
||||
{
|
||||
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
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]
|
||||
public Task Execute_Success_With_QoS_0()
|
||||
{
|
||||
@@ -135,7 +164,7 @@ namespace MQTTnet.Tests.Extensions
|
||||
|
||||
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())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "A/B/C"
|
||||
};
|
||||
@@ -318,10 +318,9 @@ namespace MQTTnet.Tests.Formatter
|
||||
var publishPacket = new MqttPublishPacket
|
||||
{
|
||||
Topic = "abcdefghijklmnopqrstuvwxyz0123456789",
|
||||
Payload = payload
|
||||
PayloadSegment = new ArraySegment<byte>(payload)
|
||||
};
|
||||
|
||||
|
||||
var serializationHelper = new MqttPacketSerializationHelper();
|
||||
|
||||
var buffer = serializationHelper.Encode(publishPacket);
|
||||
@@ -329,17 +328,17 @@ namespace MQTTnet.Tests.Formatter
|
||||
|
||||
Assert.IsNotNull(publishPacketCopy);
|
||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy.Topic);
|
||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy.Payload);
|
||||
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy.PayloadSegment.ToArray());
|
||||
|
||||
// Now modify the payload and test again.
|
||||
publishPacket.Payload = Encoding.UTF8.GetBytes("MQTT");
|
||||
publishPacket.PayloadSegment = new ArraySegment<byte>(Encoding.UTF8.GetBytes("MQTT"));
|
||||
|
||||
buffer = serializationHelper.Encode(publishPacket);
|
||||
var publishPacketCopy2 = serializationHelper.Decode(buffer) as MqttPublishPacket;
|
||||
|
||||
Assert.IsNotNull(publishPacketCopy2);
|
||||
Assert.AreEqual(publishPacket.Topic, publishPacketCopy2.Topic);
|
||||
CollectionAssert.AreEqual(publishPacket.Payload, publishPacketCopy2.Payload);
|
||||
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), publishPacketCopy2.PayloadSegment.ToArray());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -463,7 +462,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("HELLO"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("HELLO")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "A/B/C"
|
||||
};
|
||||
@@ -567,7 +566,6 @@ namespace MQTTnet.Tests.Formatter
|
||||
using (var adapter = new MqttChannelAdapter(
|
||||
channel,
|
||||
new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)),
|
||||
null,
|
||||
new MqttNetEventLogger()))
|
||||
{
|
||||
var receivedPacket = adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
@@ -583,19 +581,12 @@ namespace MQTTnet.Tests.Formatter
|
||||
MqttProtocolVersion DeserializeAndDetectVersion(MqttPacketFormatterAdapter packetFormatterAdapter, byte[] buffer)
|
||||
{
|
||||
var channel = new MemoryMqttChannel(buffer);
|
||||
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, null, new MqttNetEventLogger());
|
||||
var adapter = new MqttChannelAdapter(channel, packetFormatterAdapter, new MqttNetEventLogger());
|
||||
|
||||
adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
return packetFormatterAdapter.ProtocolVersion;
|
||||
}
|
||||
|
||||
MqttBufferReader ReaderFactory(byte[] data)
|
||||
{
|
||||
var reader = new MqttBufferReader();
|
||||
reader.SetBuffer(data, 0, data.Length);
|
||||
return reader;
|
||||
}
|
||||
|
||||
TPacket Roundtrip<TPacket>(TPacket packet, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, MqttBufferWriter bufferWriter = null) where TPacket : MqttPacket
|
||||
{
|
||||
var writer = bufferWriter ?? WriterFactory();
|
||||
@@ -604,7 +595,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
|
||||
using (var channel = new MemoryMqttChannel(buffer.Join().ToArray()))
|
||||
{
|
||||
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), null, new MqttNetEventLogger());
|
||||
var adapter = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(protocolVersion, new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
|
||||
return (TPacket)adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
@@ -76,7 +78,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.IsNull(deserialized.UserProperties); // Not supported in v3.1.1
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestMethod]
|
||||
public void Serialize_Full_MqttConnAckPacket_V310()
|
||||
{
|
||||
var connAckPacket = new MqttConnAckPacket
|
||||
@@ -296,7 +298,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "Topic",
|
||||
ResponseTopic = "/Response",
|
||||
@@ -320,7 +322,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
||||
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||
Assert.AreEqual(null, deserialized.ResponseTopic); // Not supported in v3.1.1.
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// 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.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
@@ -257,7 +259,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
PacketIdentifier = 123,
|
||||
Dup = true,
|
||||
Retain = true,
|
||||
Payload = Encoding.ASCII.GetBytes("Payload"),
|
||||
PayloadSegment = new ArraySegment<byte>(Encoding.ASCII.GetBytes("Payload")),
|
||||
QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
|
||||
Topic = "Topic",
|
||||
ResponseTopic = "/Response",
|
||||
@@ -281,7 +283,7 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(publishPacket.PacketIdentifier, deserialized.PacketIdentifier);
|
||||
Assert.AreEqual(publishPacket.Dup, deserialized.Dup);
|
||||
Assert.AreEqual(publishPacket.Retain, deserialized.Retain);
|
||||
CollectionAssert.AreEqual(publishPacket.Payload, deserialized.Payload);
|
||||
CollectionAssert.AreEqual(publishPacket.PayloadSegment.ToArray(), deserialized.PayloadSegment.ToArray());
|
||||
Assert.AreEqual(publishPacket.QualityOfServiceLevel, deserialized.QualityOfServiceLevel);
|
||||
Assert.AreEqual(publishPacket.Topic, deserialized.Topic);
|
||||
Assert.AreEqual(publishPacket.ResponseTopic, deserialized.ResponseTopic);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests.Formatter
|
||||
@@ -47,23 +48,32 @@ namespace MQTTnet.Tests.Formatter
|
||||
Assert.AreEqual(1234U, reader.ReadVariableByteInteger());
|
||||
Assert.AreEqual(9876U, reader.ReadVariableByteInteger());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Throw_If_String_Too_Long()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
|
||||
writer.WriteString(string.Empty.PadLeft(65536));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Write_And_Read_Multiple_Times()
|
||||
{
|
||||
var writer = new MqttBufferWriter(4096, 65535);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteBinaryData(new byte[1234]);
|
||||
writer.WriteBinary(new byte[1234]);
|
||||
writer.WriteByte(0x01);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteVariableByteInteger(5647382);
|
||||
writer.WriteString("A relative short string.");
|
||||
writer.WriteVariableByteInteger(8574489);
|
||||
writer.WriteBinaryData(new byte[48]);
|
||||
writer.WriteBinary(new byte[48]);
|
||||
writer.WriteByte(2);
|
||||
writer.WriteByte(0x02);
|
||||
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
|
||||
writer.WriteBinaryData(new byte[3]);
|
||||
writer.WriteBinary(new byte[3]);
|
||||
|
||||
var readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp3.1;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net48</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace MQTTnet.Tests.MQTTv5
|
||||
Assert.AreEqual(applicationMessage.ResponseTopic, receivedMessage.ResponseTopic);
|
||||
Assert.AreEqual(applicationMessage.MessageExpiryInterval, receivedMessage.MessageExpiryInterval);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public void AssertReceivedCountEquals(int expectedCount)
|
||||
{
|
||||
lock (_receivedEventArgs)
|
||||
{
|
||||
Assert.AreEqual(expectedCount, _receivedEventArgs.Count);
|
||||
}
|
||||
Assert.AreEqual(expectedCount, Count);
|
||||
}
|
||||
|
||||
public string GeneratePayloadSequence()
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
readonly List<string> _clientErrors = new List<string>();
|
||||
readonly List<IMqttClient> _clients = new List<IMqttClient>();
|
||||
|
||||
readonly List<Exception> _exceptions = new List<Exception>();
|
||||
readonly List<ILowLevelMqttClient> _lowLevelClients = new List<ILowLevelMqttClient>();
|
||||
readonly MqttProtocolVersion _protocolVersion;
|
||||
@@ -40,6 +39,8 @@ namespace MQTTnet.Tests.Mockups
|
||||
_protocolVersion = protocolVersion;
|
||||
TestContext = testContext;
|
||||
|
||||
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
|
||||
|
||||
ServerLogger.LogMessagePublished += (s, e) =>
|
||||
{
|
||||
if (Debugger.IsAttached)
|
||||
@@ -65,9 +66,12 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
if (e.LogMessage.Level == MqttNetLogLevel.Error)
|
||||
{
|
||||
lock (_clientErrors)
|
||||
if (!IgnoreClientLogErrors)
|
||||
{
|
||||
_clientErrors.Add(e.LogMessage.ToString());
|
||||
lock (_clientErrors)
|
||||
{
|
||||
_clientErrors.Add(e.LogMessage.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -212,41 +216,56 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
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)
|
||||
{
|
||||
var logger = EnableLogger ? (IMqttNetLogger)ClientLogger : new MqttNetNullLogger();
|
||||
|
||||
var client = Factory.CreateMqttClient(logger);
|
||||
_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()
|
||||
{
|
||||
return CreateDefaultClientOptionsBuilder().Build();
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder CreateDefaultClientOptionsBuilder()
|
||||
{
|
||||
return Factory.CreateClientOptionsBuilder()
|
||||
.WithProtocolVersion(_protocolVersion)
|
||||
.WithTcpServer("127.0.0.1", ServerPort)
|
||||
.WithClientId(TestContext.TestName + "_" + Guid.NewGuid());
|
||||
}
|
||||
|
||||
public ILowLevelMqttClient CreateLowLevelClient()
|
||||
{
|
||||
lock (_clients)
|
||||
{
|
||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||
_lowLevelClients.Add(client);
|
||||
var client = Factory.CreateLowLevelMqttClient(ClientLogger);
|
||||
|
||||
return client;
|
||||
lock (_lowLevelClients)
|
||||
{
|
||||
_lowLevelClients.Add(client);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public MqttServer CreateServer(MqttServerOptions options)
|
||||
@@ -280,45 +299,68 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
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
|
||||
{
|
||||
//mqttClient.DisconnectAsync().GetAwaiter().GetResult();
|
||||
Server?.StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// This can happen when the test already disconnected the client.
|
||||
// This can happen when the test already stopped the server.
|
||||
}
|
||||
finally
|
||||
{
|
||||
mqttClient?.Dispose();
|
||||
Server?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lowLevelMqttClient in _lowLevelClients)
|
||||
{
|
||||
lowLevelMqttClient.Dispose();
|
||||
}
|
||||
Server = null;
|
||||
|
||||
try
|
||||
{
|
||||
Server?.StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// This can happen when the test already stopped the server.
|
||||
ThrowIfLogErrors();
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForFullGCComplete();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
if (_exceptions.Any())
|
||||
{
|
||||
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Server?.Dispose();
|
||||
}
|
||||
|
||||
ThrowIfLogErrors();
|
||||
|
||||
if (_exceptions.Any())
|
||||
{
|
||||
throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
|
||||
TaskScheduler.UnobservedTaskException -= TrackUnobservedTaskException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +377,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
var options = optionsBuilder.Build();
|
||||
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.
|
||||
ServerPort = options.DefaultEndpointOptions.Port;
|
||||
@@ -363,33 +405,49 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
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)}).";
|
||||
Console.WriteLine(message);
|
||||
throw new Exception(message);
|
||||
if (_serverErrors.Count > 0)
|
||||
{
|
||||
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)})";
|
||||
Console.WriteLine(message);
|
||||
throw new Exception(message);
|
||||
if (_clientErrors.Count > 0)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_exceptions)
|
||||
{
|
||||
_exceptions.Add(exception);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
|
||||
{
|
||||
TrackException(e.Exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Protocol;
|
||||
@@ -11,7 +12,7 @@ using MQTTnet.Protocol;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttApplicationMessageBuilder_Tests
|
||||
public sealed class MqttApplicationMessageBuilder_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CreateApplicationMessage_TopicOnly()
|
||||
@@ -29,7 +30,7 @@ namespace MQTTnet.Tests
|
||||
Assert.AreEqual("xyz", message.Topic);
|
||||
Assert.IsFalse(message.Retain);
|
||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "00:06:00");
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "00:06:00");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -41,7 +42,7 @@ namespace MQTTnet.Tests
|
||||
Assert.AreEqual("123", message.Topic);
|
||||
Assert.IsFalse(message.Retain);
|
||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtMostOnce, message.QualityOfServiceLevel);
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(message.Payload), "Hello");
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(message.PayloadSegment.ToArray()), "Hello");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttApplicationMessageTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void PayloadSegment()
|
||||
{
|
||||
var message = new MqttApplicationMessage();
|
||||
Assert.AreEqual(0, message.Payload.Length);
|
||||
Assert.AreEqual(0, message.PayloadSegment.Count);
|
||||
|
||||
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||
|
||||
message.Payload = new byte[] { 1, 2 };
|
||||
Assert.AreEqual(2, message.Payload.Length);
|
||||
Assert.AreEqual(2, message.PayloadSegment.Count);
|
||||
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||
|
||||
message.Payload = new byte[] { 1, 2, 3 };
|
||||
Assert.AreEqual(3, message.Payload.Length);
|
||||
Assert.AreEqual(3, message.PayloadSegment.Count);
|
||||
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||
|
||||
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 });
|
||||
Assert.AreEqual(3, message.Payload.Length);
|
||||
Assert.AreEqual(3, message.PayloadSegment.Count);
|
||||
Assert.IsTrue(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||
|
||||
message.PayloadSegment = new ArraySegment<byte>(new byte[] { 1, 2, 3 }, 1, 1);
|
||||
Assert.AreEqual(1, message.Payload.Length);
|
||||
Assert.AreEqual(1, message.PayloadSegment.Count);
|
||||
Assert.IsFalse(ReferenceEquals(message.Payload, message.PayloadSegment.Array));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttApplicationMessageValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V311);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_TopicAlias_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(new MqttApplicationMessageBuilder().WithTopicAlias(1).Build(), MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("A").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V500);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(
|
||||
new MqttApplicationMessageBuilder().WithTopic("B").WithUserProperty("User", "Property").Build(),
|
||||
MqttProtocolVersion.V311);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class MqttClientOptionsValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_UserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Succeed_When_Using_WillUserProperties_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithWillUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_UserProperties_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithUserProperty("User", "Property").WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(NotSupportedException))]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_311()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V311).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Throw_When_Using_WithRequestResponseInformation_And_MQTT_500()
|
||||
{
|
||||
new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500).WithRequestResponseInformation().WithTcpServer("FAKE").Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests
|
||||
{
|
||||
var formatterAdapter = new MqttPacketFormatterAdapter(_protocolVersion, new MqttBufferWriter(4096, 65535));
|
||||
|
||||
var adapter = new MqttChannelAdapter(channel, formatterAdapter, null, MqttNetNullLogger.Instance);
|
||||
var adapter = new MqttChannelAdapter(channel, formatterAdapter, MqttNetNullLogger.Instance);
|
||||
return adapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace MQTTnet.Tests
|
||||
writer.WriteString("1234567890");
|
||||
Assert.AreEqual(10 + 2, writer.Length);
|
||||
|
||||
writer.WriteBinaryData(new byte[300]);
|
||||
writer.WriteBinary(new byte[300]);
|
||||
Assert.AreEqual(300 + 2 + 12, writer.Length);
|
||||
|
||||
writer.WriteBinaryData(new byte[5000]);
|
||||
writer.WriteBinary(new byte[5000]);
|
||||
Assert.AreEqual(5000 + 2 + 300 + 2 + 12, writer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,13 @@ using MQTTnet.Protocol;
|
||||
namespace MQTTnet.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class MqttTopicValidator_Tests
|
||||
public sealed class MqttTopicValidator_Tests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Plus()
|
||||
public void Invalid_Topic_Empty()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -33,9 +27,15 @@ namespace MQTTnet.Tests
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttProtocolViolationException))]
|
||||
public void Invalid_Topic_Empty()
|
||||
public void Invalid_Topic_Plus()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid(string.Empty);
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/+/c");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Valid_Topic()
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalid("/a/b/c");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,8 +598,7 @@ namespace MQTTnet.Tests.Server
|
||||
// c
|
||||
var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
var flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("c", flow);
|
||||
|
||||
@@ -607,10 +606,8 @@ namespace MQTTnet.Tests.Server
|
||||
// Connect client with same client ID. Should disconnect existing client.
|
||||
var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder);
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
|
||||
Assert.AreEqual("cdc", flow);
|
||||
|
||||
c2.ApplicationMessageReceivedAsync += e =>
|
||||
@@ -628,27 +625,27 @@ namespace MQTTnet.Tests.Server
|
||||
// r
|
||||
await c2.PublishStringAsync("topic");
|
||||
|
||||
await Task.Delay(500);
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("cdcr", flow);
|
||||
|
||||
// nothing
|
||||
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
await c1.DisconnectAsync();
|
||||
await c1.TryDisconnectAsync();
|
||||
Assert.AreEqual(false, c1.IsConnected);
|
||||
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
// d
|
||||
Assert.AreEqual(true, c2.IsConnected);
|
||||
await c2.DisconnectAsync();
|
||||
|
||||
await Task.Delay(500);
|
||||
await LongTestDelay();
|
||||
|
||||
await server.StopAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
flow = string.Join(string.Empty, events);
|
||||
Assert.AreEqual("cdcrd", flow);
|
||||
}
|
||||
@@ -852,11 +849,11 @@ namespace MQTTnet.Tests.Server
|
||||
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 Task.Delay(100);
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, disconnectCalled);
|
||||
}
|
||||
@@ -954,7 +951,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
client.InspectPackage += e =>
|
||||
client.InspectPacketAsync += e =>
|
||||
{
|
||||
if (e.Buffer.Length > 0)
|
||||
{
|
||||
|
||||
@@ -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,14 +221,17 @@ namespace MQTTnet.Tests.Server
|
||||
var eventContainer = new MqttServerEventContainer();
|
||||
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);
|
||||
}
|
||||
|
||||
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
||||
{
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +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.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class Publish_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Return_Success_When_Subscribed()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
await receiver.SubscribeAsync("A");
|
||||
|
||||
// AtLeastOnce is required to get an ACK packet from the server.
|
||||
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
||||
|
||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
await receiver.SubscribeAsync("A");
|
||||
|
||||
// AtLeastOnce is required to get an ACK packet from the server.
|
||||
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
||||
|
||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
@@ -27,5 +29,118 @@ namespace MQTTnet.Tests.Server
|
||||
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
await receiver.SubscribeAsync("A");
|
||||
|
||||
// AtLeastOnce is required to get an ACK packet from the server.
|
||||
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
||||
|
||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Return_Success_When_Subscribed()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
await receiver.SubscribeAsync("A");
|
||||
|
||||
// AtLeastOnce is required to get an ACK packet from the server.
|
||||
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
||||
|
||||
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Client_Enqueue()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
await receiver.SubscribeAsync("A");
|
||||
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
|
||||
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
|
||||
server.InterceptingClientEnqueueAsync += e =>
|
||||
{
|
||||
e.AcceptEnqueue = false;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
// Do not increase because the internal enqueue to the target client is not accepted!
|
||||
receivedMessages.AssertReceivedCountEquals(1);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Client_Enqueue_Multiple_Clients_Subscribed_Messages_Are_Filtered()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var sender = await testEnvironment.ConnectClient();
|
||||
|
||||
var receiverOne = await testEnvironment.ConnectClient(o => o.WithClientId("One"));
|
||||
await receiverOne.SubscribeAsync("A");
|
||||
var receivedMessagesOne = testEnvironment.CreateApplicationMessageHandler(receiverOne);
|
||||
|
||||
var receiverTwo = await testEnvironment.ConnectClient(o => o.WithClientId("Two"));
|
||||
await receiverTwo.SubscribeAsync("A");
|
||||
var receivedMessagesTwo = testEnvironment.CreateApplicationMessageHandler(receiverTwo);
|
||||
|
||||
var receiverThree = await testEnvironment.ConnectClient(o => o.WithClientId("Three"));
|
||||
await receiverThree.SubscribeAsync("A");
|
||||
var receivedMessagesThree = testEnvironment.CreateApplicationMessageHandler(receiverThree);
|
||||
|
||||
server.InterceptingClientEnqueueAsync += e =>
|
||||
{
|
||||
if (e.ReceiverClientId.Contains("Two")) e.AcceptEnqueue = false;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
receivedMessagesOne.AssertReceivedCountEquals(1);
|
||||
receivedMessagesTwo.AssertReceivedCountEquals(0);
|
||||
receivedMessagesThree.AssertReceivedCountEquals(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithCredentials(userName, password).Build();
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
|
||||
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => client.ConnectAsync(clientOptions));
|
||||
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
public async Task Fire_Deleted_Event()
|
||||
{
|
||||
@@ -121,7 +143,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(MqttProtocolVersion.V310)]
|
||||
[DataRow(MqttProtocolVersion.V311)]
|
||||
@@ -134,16 +156,12 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
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;
|
||||
|
||||
void OnReceive()
|
||||
{
|
||||
if (!hasReceive)
|
||||
{
|
||||
hasReceive = true;
|
||||
}
|
||||
hasReceive = true;
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
|
||||
Assert.AreEqual(1, connectedClients.Count);
|
||||
|
||||
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
|
||||
@@ -164,7 +182,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.AreEqual(true, hasReceive);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||
@@ -355,8 +373,8 @@ namespace MQTTnet.Tests.Server
|
||||
onReceive();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace MQTTnet.Tests.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deny_Invalid_Topic()
|
||||
{
|
||||
@@ -90,6 +90,29 @@ namespace MQTTnet.Tests.Server
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Subscribe_With_User_Properties()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
InterceptingSubscriptionEventArgs eventArgs = null;
|
||||
server.InterceptingSubscriptionAsync += e =>
|
||||
{
|
||||
eventArgs = e;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
|
||||
await client.SubscribeAsync(subscribeOptions);
|
||||
|
||||
CollectionAssert.AreEqual(subscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||
public async Task Disconnect_While_Subscribing()
|
||||
@@ -105,7 +128,7 @@ namespace MQTTnet.Tests.Server
|
||||
await client.SubscribeAsync("#");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueue_Message_After_Subscription()
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
|
||||
var topics = t.Value;
|
||||
foreach (var topic in topics)
|
||||
{
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||
|
||||
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
||||
++currentValue;
|
||||
@@ -96,7 +96,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
}
|
||||
@@ -110,7 +110,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
}
|
||||
@@ -150,7 +150,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
// UInt64 is limited to 8 levels
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
var topic = sb.ToString();
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
||||
|
||||
@@ -269,7 +269,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
var topic = sb.ToString();
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace MQTTnet.Tests.Server
|
||||
var l3 = "#";
|
||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
|
||||
@@ -354,7 +354,7 @@ namespace MQTTnet.Tests.Server
|
||||
var l3 = "prop1";
|
||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
||||
|
||||
@@ -447,7 +447,7 @@ namespace MQTTnet.Tests.Server
|
||||
var l3 = "prop1";
|
||||
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||
|
||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||
|
||||
@@ -475,7 +475,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
||||
{
|
||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("Topic: " + topic);
|
||||
@@ -501,7 +501,7 @@ namespace MQTTnet.Tests.Server
|
||||
{
|
||||
var topicToFind = topicsToFind[countDown];
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
|
||||
MqttTopicHash.Calculate(topicToFind, out var topicHash, out _, out _);
|
||||
|
||||
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
||||
if (result.IsSubscribed)
|
||||
@@ -557,7 +557,7 @@ namespace MQTTnet.Tests.Server
|
||||
var eventContainer = new MqttServerEventContainer();
|
||||
var retainedMessagesManager = new MqttRetainedMessagesManager(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;
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
#if !WINDOWS_UWP && (NET48_OR_GREATER || NET5_0_OR_GREATER)
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
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.Server;
|
||||
using MQTTnet.Tests.Mockups;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class Tls_Tests : BaseTestClass
|
||||
{
|
||||
static X509Certificate2 CreateCertificate(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Tls_Swap_Test()
|
||||
{
|
||||
var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var serverOptionsBuilder = testEnvironment.Factory.CreateServerOptionsBuilder();
|
||||
|
||||
var firstOid = "1.3.6.1.5.5.7.3.1";
|
||||
var secondOid = "1.3.6.1.5.5.7.3.2";
|
||||
|
||||
var certificateProvider = new CertificateProvider
|
||||
{
|
||||
CurrentCertificate = CreateCertificate(firstOid)
|
||||
};
|
||||
|
||||
serverOptionsBuilder.WithoutDefaultEndpoint().WithEncryptedEndpoint().WithEncryptionSslProtocol(SslProtocols.Tls12).WithEncryptionCertificate(certificateProvider);
|
||||
|
||||
var serverOptions = serverOptionsBuilder.Build();
|
||||
|
||||
var server = testEnvironment.CreateServer(serverOptions);
|
||||
|
||||
var publishedCount = 0;
|
||||
server.InterceptingPublishAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref publishedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
var firstClient = await ConnectClientAsync(
|
||||
testEnvironment,
|
||||
args =>
|
||||
{
|
||||
Assert.AreEqual(firstOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||
return true;
|
||||
});
|
||||
|
||||
var firstClientReceivedCount = 0;
|
||||
firstClient.ApplicationMessageReceivedAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref firstClientReceivedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await firstClient.SubscribeAsync("TestTopic1");
|
||||
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
certificateProvider.CurrentCertificate = CreateCertificate(secondOid);
|
||||
|
||||
// Validate that the certificate was switched
|
||||
var secondClient = await ConnectClientAsync(
|
||||
testEnvironment,
|
||||
args =>
|
||||
{
|
||||
Assert.AreEqual(secondOid, ((X509Certificate2)args.Certificate).Extensions.OfType<X509EnhancedKeyUsageExtension>().First().EnhancedKeyUsages[0].Value);
|
||||
return true;
|
||||
});
|
||||
|
||||
var secondClientReceivedCount = 0;
|
||||
secondClient.ApplicationMessageReceivedAsync += args =>
|
||||
{
|
||||
Interlocked.Increment(ref secondClientReceivedCount);
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
await secondClient.SubscribeAsync("TestTopic2");
|
||||
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic2",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic2",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
// Ensure first client still works
|
||||
await firstClient.PublishAsync(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
});
|
||||
|
||||
await testEnvironment.Server.InjectApplicationMessage(
|
||||
new InjectedMqttApplicationMessage(
|
||||
new MqttApplicationMessage
|
||||
{
|
||||
Topic = "TestTopic1",
|
||||
Payload = new byte[] { 1, 2, 3, 4 }
|
||||
}));
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
Assert.AreEqual(6, publishedCount);
|
||||
Assert.AreEqual(4, firstClientReceivedCount);
|
||||
Assert.AreEqual(2, secondClientReceivedCount);
|
||||
|
||||
await server.StopAsync();
|
||||
}
|
||||
|
||||
static async Task<IMqttClient> ConnectClientAsync(TestEnvironment testEnvironment, Func<MqttClientCertificateValidationEventArgs, bool> certValidator)
|
||||
{
|
||||
var clientOptionsBuilder = testEnvironment.Factory.CreateClientOptionsBuilder();
|
||||
clientOptionsBuilder.WithClientId(Guid.NewGuid().ToString())
|
||||
.WithTcpServer("localhost", 8883)
|
||||
.WithTls(
|
||||
tls =>
|
||||
{
|
||||
tls.UseTls = true;
|
||||
tls.SslProtocol = SslProtocols.Tls12;
|
||||
tls.CertificateValidationHandler = certValidator;
|
||||
});
|
||||
|
||||
var clientOptions = clientOptionsBuilder.Build();
|
||||
return await testEnvironment.ConnectClient(clientOptions);
|
||||
}
|
||||
|
||||
sealed class CertificateProvider : ICertificateProvider
|
||||
{
|
||||
public X509Certificate2 CurrentCertificate { get; set; }
|
||||
|
||||
public X509Certificate2 GetCertificate()
|
||||
{
|
||||
return CurrentCertificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2,10 +2,14 @@
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Client;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
@@ -28,5 +32,28 @@ namespace MQTTnet.Tests.Server
|
||||
await client.UnsubscribeAsync("#");
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Unsubscribe_With_User_Properties()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
InterceptingUnsubscriptionEventArgs eventArgs = null;
|
||||
server.InterceptingUnsubscriptionAsync += e =>
|
||||
{
|
||||
eventArgs = e;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var unsubscribeOptions = testEnvironment.Factory.CreateUnsubscribeOptionsBuilder().WithTopicFilter("X").WithUserProperty("A", "1").Build();
|
||||
await client.UnsubscribeAsync(unsubscribeOptions);
|
||||
|
||||
CollectionAssert.AreEqual(unsubscribeOptions.UserProperties.ToList(), eventArgs.UserProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,18 +150,8 @@ namespace MQTTnet.Tests
|
||||
{
|
||||
Assert.AreEqual(expectedResult, MqttTopicFilterComparer.Compare(topic, filter));
|
||||
|
||||
ulong topicHash;
|
||||
ulong topicHashMask;
|
||||
bool topicHasWildcard;
|
||||
|
||||
MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
||||
|
||||
|
||||
ulong filterTopicHash;
|
||||
ulong filterTopicHashMask;
|
||||
bool filterTopicHasWildcard;
|
||||
|
||||
MqttSubscription.CalculateTopicHash(filter, out filterTopicHash, out filterTopicHashMask, out filterTopicHasWildcard);
|
||||
MqttTopicHash.Calculate(topic, out var topicHash, out _, out _);
|
||||
MqttTopicHash.Calculate(filter, out var filterTopicHash, out var filterTopicHashMask, out _);
|
||||
|
||||
if (expectedResult == MqttTopicFilterCompareResult.IsMatch)
|
||||
{
|
||||
|
||||
@@ -26,20 +26,14 @@ namespace MQTTnet.Adapter
|
||||
readonly IMqttChannel _channel;
|
||||
readonly byte[] _fixedHeaderBuffer = new byte[2];
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly MqttPacketInspector _packetInspector;
|
||||
|
||||
readonly byte[] _singleByteBuffer = new byte[1];
|
||||
|
||||
readonly AsyncLock _syncRoot = new AsyncLock();
|
||||
|
||||
long _bytesReceived;
|
||||
long _bytesSent;
|
||||
Statistics _statistics; // mutable struct, don't make readonly!
|
||||
|
||||
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||
public MqttChannelAdapter(IMqttChannel channel, MqttPacketFormatterAdapter packetFormatterAdapter, IMqttNetLogger logger)
|
||||
{
|
||||
_channel = channel ?? throw new ArgumentNullException(nameof(channel));
|
||||
_packetInspector = packetInspector;
|
||||
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
|
||||
@@ -51,9 +45,11 @@ namespace MQTTnet.Adapter
|
||||
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
||||
}
|
||||
|
||||
public long BytesReceived => Interlocked.Read(ref _bytesReceived);
|
||||
public bool AllowPacketFragmentation { get; set; } = true;
|
||||
|
||||
public long BytesSent => Interlocked.Read(ref _bytesSent);
|
||||
public long BytesReceived => Volatile.Read(ref _statistics._bytesReceived);
|
||||
|
||||
public long BytesSent => Volatile.Read(ref _statistics._bytesSent);
|
||||
|
||||
public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
|
||||
|
||||
@@ -65,6 +61,8 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||
|
||||
public MqttPacketInspector PacketInspector { get; set; }
|
||||
|
||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -78,12 +76,30 @@ namespace MQTTnet.Adapter
|
||||
* block forever. Even a cancellation token is not supported properly.
|
||||
*/
|
||||
|
||||
var connectTask = _channel.ConnectAsync(cancellationToken);
|
||||
|
||||
var timeout = new TaskCompletionSource<object>();
|
||||
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);
|
||||
|
||||
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
|
||||
{
|
||||
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
|
||||
@@ -127,7 +143,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
try
|
||||
{
|
||||
_packetInspector?.BeginReceivePacket();
|
||||
PacketInspector?.BeginReceivePacket();
|
||||
|
||||
ReceivedMqttPacket receivedPacket;
|
||||
var receivedPacketTask = ReceiveAsync(cancellationToken);
|
||||
@@ -145,9 +161,9 @@ namespace MQTTnet.Adapter
|
||||
return null;
|
||||
}
|
||||
|
||||
_packetInspector?.EndReceivePacket();
|
||||
PacketInspector?.EndReceivePacket();
|
||||
|
||||
Interlocked.Add(ref _bytesSent, receivedPacket.TotalLength);
|
||||
Interlocked.Add(ref _statistics._bytesSent, receivedPacket.TotalLength);
|
||||
|
||||
if (PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||
{
|
||||
@@ -183,8 +199,7 @@ namespace MQTTnet.Adapter
|
||||
|
||||
public void ResetStatistics()
|
||||
{
|
||||
Interlocked.Exchange(ref _bytesReceived, 0L);
|
||||
Interlocked.Exchange(ref _bytesSent, 0L);
|
||||
_statistics.Reset();
|
||||
}
|
||||
|
||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
@@ -202,21 +217,21 @@ namespace MQTTnet.Adapter
|
||||
try
|
||||
{
|
||||
var packetBuffer = PacketFormatterAdapter.Encode(packet);
|
||||
_packetInspector?.BeginSendPacket(packetBuffer);
|
||||
PacketInspector?.BeginSendPacket(packetBuffer);
|
||||
|
||||
_logger.Verbose("TX ({0} bytes) >>> {1}", packetBuffer.Length, packet);
|
||||
|
||||
if (packetBuffer.Payload.Count > 0)
|
||||
if (packetBuffer.Payload.Count == 0 || !AllowPacketFragmentation)
|
||||
{
|
||||
await _channel.WriteAsync(packetBuffer.Join(), true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _channel.WriteAsync(packetBuffer.Packet, false, cancellationToken).ConfigureAwait(false);
|
||||
await _channel.WriteAsync(packetBuffer.Payload, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _channel.WriteAsync(packetBuffer.Packet, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _bytesReceived, packetBuffer.Length);
|
||||
Interlocked.Add(ref _statistics._bytesReceived, packetBuffer.Length);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -275,7 +290,7 @@ namespace MQTTnet.Adapter
|
||||
return 0;
|
||||
}
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||
PacketInspector?.FillReceiveBuffer(_singleByteBuffer);
|
||||
|
||||
encodedByte = _singleByteBuffer[0];
|
||||
|
||||
@@ -314,7 +329,7 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
return ReadFixedHeaderResult.ConnectionClosed;
|
||||
}
|
||||
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return ReadFixedHeaderResult.Canceled;
|
||||
@@ -328,7 +343,7 @@ namespace MQTTnet.Adapter
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(buffer);
|
||||
PacketInspector?.FillReceiveBuffer(buffer);
|
||||
|
||||
var hasRemainingLength = buffer[1] != 0;
|
||||
if (!hasRemainingLength)
|
||||
@@ -412,7 +427,7 @@ namespace MQTTnet.Adapter
|
||||
bodyOffset += readBytes;
|
||||
} while (bodyOffset < bodyLength);
|
||||
|
||||
_packetInspector?.FillReceiveBuffer(body);
|
||||
PacketInspector?.FillReceiveBuffer(body);
|
||||
|
||||
var bodySegment = new ArraySegment<byte>(body, 0, bodyLength);
|
||||
return new ReceivedMqttPacket(fixedHeader.Flags, bodySegment, fixedHeader.TotalLength);
|
||||
@@ -459,5 +474,17 @@ namespace MQTTnet.Adapter
|
||||
|
||||
throw new MqttCommunicationException(exception);
|
||||
}
|
||||
|
||||
struct Statistics
|
||||
{
|
||||
public long _bytesReceived;
|
||||
public long _bytesSent;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Volatile.Write(ref _bytesReceived, 0);
|
||||
Volatile.Write(ref _bytesSent, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,12 +82,7 @@ namespace MQTTnet.Adapter
|
||||
{
|
||||
try
|
||||
{
|
||||
var eventArgs = new InspectMqttPacketEventArgs
|
||||
{
|
||||
Buffer = buffer,
|
||||
Direction = direction
|
||||
};
|
||||
|
||||
var eventArgs = new InspectMqttPacketEventArgs(direction, buffer);
|
||||
_asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
}
|
||||
|
||||
@@ -2,20 +2,35 @@
|
||||
// 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;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason code.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; set; } = MqttClientDisconnectReason.NormalDisconnection;
|
||||
public MqttClientDisconnectOptionsReason Reason { get; set; } = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason string.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,32 +2,62 @@
|
||||
// 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;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectOptionsBuilder
|
||||
{
|
||||
MqttClientDisconnectReason _reason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
MqttClientDisconnectOptionsReason _reason = MqttClientDisconnectOptionsReason.NormalDisconnection;
|
||||
string _reasonString;
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
uint _sessionExpiryInterval;
|
||||
List<MqttUserProperty> _userProperties;
|
||||
|
||||
public MqttClientDisconnectOptions Build()
|
||||
{
|
||||
return new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = _reason,
|
||||
ReasonString = _reasonString
|
||||
ReasonString = _reasonString,
|
||||
UserProperties = _userProperties,
|
||||
SessionExpiryInterval = _sessionExpiryInterval
|
||||
};
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReason(MqttClientDisconnectOptionsReason value)
|
||||
{
|
||||
_reason = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithReasonString(string value)
|
||||
{
|
||||
_reasonString = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithSessionExpiryInterval(uint value)
|
||||
{
|
||||
_sessionExpiryInterval = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_userProperties = userProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (_userProperties == null)
|
||||
{
|
||||
_userProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_userProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// This enum only contains values which are valid when a client sends the reason to the server.
|
||||
/// </summary>
|
||||
public enum MqttClientDisconnectOptionsReason
|
||||
{
|
||||
NormalDisconnection = 0,
|
||||
DisconnectWithWillMessage = 4,
|
||||
UnspecifiedError = 128,
|
||||
MalformedPacket = 129,
|
||||
ProtocolError = 130,
|
||||
ImplementationSpecificError = 131,
|
||||
TopicNameInvalid = 144,
|
||||
ReceiveMaximumExceeded = 147,
|
||||
TopicAliasInvalid = 148,
|
||||
PacketTooLarge = 149,
|
||||
MessageRateTooHigh = 150,
|
||||
QuotaExceeded = 151,
|
||||
AdministrativeAction = 152,
|
||||
PayloadFormatInvalid = 153
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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 MQTTnet.Formatter;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientDisconnectOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
// Everything is supported.
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.ReasonString?.Any() == true)
|
||||
{
|
||||
Throw(nameof(options.ReasonString));
|
||||
}
|
||||
|
||||
if (options.Reason != MqttClientDisconnectOptionsReason.NormalDisconnection)
|
||||
{
|
||||
Throw(nameof(options.Reason));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
{
|
||||
throw new NotSupportedException($"Feature {featureName} requires MQTT version 5.0.0.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,27 +3,47 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public bool ClientWasConnected { get; internal set; }
|
||||
public MqttClientDisconnectedEventArgs(
|
||||
bool clientWasConnected,
|
||||
MqttClientConnectResult connectResult,
|
||||
MqttClientDisconnectReason reason,
|
||||
string reasonString,
|
||||
List<MqttUserProperty> userProperties,
|
||||
Exception exception)
|
||||
{
|
||||
ClientWasConnected = clientWasConnected;
|
||||
ConnectResult = connectResult;
|
||||
Exception = exception;
|
||||
Reason = reason;
|
||||
ReasonString = reasonString;
|
||||
UserProperties = userProperties;
|
||||
}
|
||||
|
||||
public Exception Exception { get; internal set; }
|
||||
public bool ClientWasConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication result.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets the authentication result.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientConnectResult ConnectResult { get; internal set; }
|
||||
public MqttClientConnectResult ConnectResult { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// Gets or sets the reason.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttClientDisconnectReason Reason { get; internal set; }
|
||||
|
||||
public string ReasonString { get; internal set; }
|
||||
public MqttClientDisconnectReason Reason { get; }
|
||||
|
||||
public string ReasonString { get; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Client
|
||||
|
||||
event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync;
|
||||
|
||||
event Func<InspectMqttPacketEventArgs, Task> InspectPackage;
|
||||
event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync;
|
||||
|
||||
bool IsConnected { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Diagnostics;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
namespace MQTTnet.Client.Internal
|
||||
{
|
||||
public sealed class MqttClientEvents
|
||||
{
|
||||
public AsyncEvent<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceivedEvent { get; } = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||
public AsyncEvent<MqttClientConnectedEventArgs> ConnectedEvent { get; } = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
public AsyncEvent<MqttClientConnectingEventArgs> ConnectingEvent { get; } = new AsyncEvent<MqttClientConnectingEventArgs>();
|
||||
public AsyncEvent<MqttClientDisconnectedEventArgs> DisconnectedEvent { get; } = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
public AsyncEvent<InspectMqttPacketEventArgs> InspectPacketEvent { get; } = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.Internal
|
||||
{
|
||||
public static class MqttClientResultFactory
|
||||
{
|
||||
public static readonly MqttClientConnectResultFactory ConnectResult = new MqttClientConnectResultFactory();
|
||||
public static readonly MqttClientPublishResultFactory PublishResult = new MqttClientPublishResultFactory();
|
||||
public static readonly MqttClientSubscribeResultFactory SubscribeResult = new MqttClientSubscribeResultFactory();
|
||||
public static readonly MqttClientUnsubscribeResultFactory UnsubscribeResult = new MqttClientUnsubscribeResultFactory();
|
||||
}
|
||||
}
|
||||
+362
-234
@@ -3,9 +3,11 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Client.Internal;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
@@ -19,30 +21,29 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClient : Disposable, IMqttClient
|
||||
{
|
||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||
|
||||
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
||||
readonly MqttClientSubscribeResultFactory _clientSubscribeResultFactory = new MqttClientSubscribeResultFactory();
|
||||
readonly MqttClientUnsubscribeResultFactory _clientUnsubscribeResultFactory = new MqttClientUnsubscribeResultFactory();
|
||||
|
||||
readonly AsyncEvent<MqttClientConnectedEventArgs> _connectedEvent = new AsyncEvent<MqttClientConnectedEventArgs>();
|
||||
readonly AsyncEvent<MqttClientConnectingEventArgs> _connectingEvent = new AsyncEvent<MqttClientConnectingEventArgs>();
|
||||
readonly AsyncEvent<MqttClientDisconnectedEventArgs> _disconnectedEvent = new AsyncEvent<MqttClientDisconnectedEventArgs>();
|
||||
readonly object _disconnectLock = new object();
|
||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||
readonly MqttClientEvents _events = new MqttClientEvents();
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
|
||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
IMqttChannelAdapter _adapter;
|
||||
CancellationTokenSource _backgroundCancellationTokenSource;
|
||||
|
||||
bool _cleanDisconnectInitiated;
|
||||
volatile int _connectionStatus;
|
||||
MqttClientDisconnectReason _disconnectReason;
|
||||
|
||||
// The value for this field can be set from two different enums.
|
||||
// They contain the same values but the set is reduced in one case.
|
||||
int _disconnectReason;
|
||||
string _disconnectReasonString;
|
||||
List<MqttUserProperty> _disconnectUserProperties;
|
||||
|
||||
Task _keepAlivePacketsSenderTask;
|
||||
DateTime _lastPacketSentTimestamp;
|
||||
|
||||
CancellationTokenSource _mqttClientAlive;
|
||||
MqttPacketDispatcher _packetDispatcher;
|
||||
Task _packetReceiverTask;
|
||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||
@@ -57,52 +58,42 @@ namespace MQTTnet.Client
|
||||
|
||||
public event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync
|
||||
{
|
||||
add => _applicationMessageReceivedEvent.AddHandler(value);
|
||||
remove => _applicationMessageReceivedEvent.RemoveHandler(value);
|
||||
add => _events.ApplicationMessageReceivedEvent.AddHandler(value);
|
||||
remove => _events.ApplicationMessageReceivedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync
|
||||
{
|
||||
add => _connectedEvent.AddHandler(value);
|
||||
remove => _connectedEvent.RemoveHandler(value);
|
||||
add => _events.ConnectedEvent.AddHandler(value);
|
||||
remove => _events.ConnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync
|
||||
{
|
||||
add => _connectingEvent.AddHandler(value);
|
||||
remove => _connectingEvent.RemoveHandler(value);
|
||||
add => _events.ConnectingEvent.AddHandler(value);
|
||||
remove => _events.ConnectingEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<MqttClientDisconnectedEventArgs, Task> DisconnectedAsync
|
||||
{
|
||||
add => _disconnectedEvent.AddHandler(value);
|
||||
remove => _disconnectedEvent.RemoveHandler(value);
|
||||
add => _events.DisconnectedEvent.AddHandler(value);
|
||||
remove => _events.DisconnectedEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPackage
|
||||
public event Func<InspectMqttPacketEventArgs, Task> InspectPacketAsync
|
||||
{
|
||||
add => _inspectPacketEvent.AddHandler(value);
|
||||
remove => _inspectPacketEvent.RemoveHandler(value);
|
||||
add => _events.InspectPacketEvent.AddHandler(value);
|
||||
remove => _events.InspectPacketEvent.RemoveHandler(value);
|
||||
}
|
||||
|
||||
|
||||
public bool IsConnected => (MqttClientConnectionStatus)_connectionStatus == MqttClientConnectionStatus.Connected;
|
||||
|
||||
public MqttClientOptions Options { get; private set; }
|
||||
|
||||
public async Task<MqttClientConnectResult> ConnectAsync(MqttClientOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
ThrowIfOptionsInvalid(options);
|
||||
ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");
|
||||
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
|
||||
@@ -116,33 +107,41 @@ namespace MQTTnet.Client
|
||||
{
|
||||
Options = options;
|
||||
|
||||
if (_connectingEvent.HasHandlers)
|
||||
if (_events.ConnectingEvent.HasHandlers)
|
||||
{
|
||||
await _connectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
await _events.ConnectingEvent.InvokeAsync(new MqttClientConnectingEventArgs(options));
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
|
||||
_packetIdentifierProvider.Reset();
|
||||
_packetDispatcher = new MqttPacketDispatcher();
|
||||
|
||||
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
||||
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
||||
_mqttClientAlive = new CancellationTokenSource();
|
||||
var mqttClientAliveToken = _mqttClientAlive.Token;
|
||||
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_inspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter;
|
||||
var adapter = _adapterFactory.CreateClientAdapter(options, new MqttPacketInspector(_events.InspectPacketEvent, _rootLogger), _rootLogger);
|
||||
_adapter = adapter ?? throw new InvalidOperationException("The adapter factory did not provide an adapter.");
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'.", options.ChannelOptions);
|
||||
await adapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established.");
|
||||
connectResult = await ConnectInternal(adapter, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to the general timeout specified in the options if the user passed
|
||||
// CancellationToken.None or similar.
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
connectResult = await ConnectInternal(adapter, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
_packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
|
||||
connectResult = await AuthenticateAsync(options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
if (connectResult.ResultCode != MqttClientConnectResultCode.Success)
|
||||
{
|
||||
_logger.Warning("Connecting failed: {0}", connectResult.ResultCode);
|
||||
await DisconnectInternal(null, null, connectResult).ConfigureAwait(false);
|
||||
return connectResult;
|
||||
}
|
||||
|
||||
_lastPacketSentTimestamp = DateTime.UtcNow;
|
||||
@@ -150,34 +149,35 @@ namespace MQTTnet.Client
|
||||
var keepAliveInterval = Options.KeepAlivePeriod;
|
||||
if (connectResult.ServerKeepAlive > 0)
|
||||
{
|
||||
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server.");
|
||||
_logger.Info($"Using keep alive value ({connectResult.ServerKeepAlive}) sent from the server");
|
||||
keepAliveInterval = TimeSpan.FromSeconds(connectResult.ServerKeepAlive);
|
||||
}
|
||||
|
||||
if (keepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessages(mqttClientAliveToken), mqttClientAliveToken);
|
||||
}
|
||||
|
||||
CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);
|
||||
|
||||
_logger.Info("Connected.");
|
||||
_logger.Info("Connected");
|
||||
|
||||
if (_connectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
await _connectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
await OnConnected(connectResult).ConfigureAwait(false);
|
||||
|
||||
return connectResult;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
|
||||
if (exception is MqttConnectingFailedException connectingFailedException)
|
||||
{
|
||||
connectResult = connectingFailedException.Result;
|
||||
}
|
||||
|
||||
_logger.Error(exception, "Error while connecting with server.");
|
||||
_disconnectReason = (int)MqttClientDisconnectOptionsReason.UnspecifiedError;
|
||||
|
||||
await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);
|
||||
_logger.Error(exception, "Error while connecting with server");
|
||||
|
||||
await DisconnectInternal(null, exception, connectResult).ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
@@ -193,6 +193,7 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return;
|
||||
@@ -200,32 +201,57 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_disconnectReason = MqttClientDisconnectReason.NormalDisconnection;
|
||||
if (!clientWasConnected)
|
||||
{
|
||||
ThrowNotConnected();
|
||||
}
|
||||
|
||||
_disconnectReason = (int)options.Reason;
|
||||
_cleanDisconnectInitiated = true;
|
||||
|
||||
if (clientWasConnected)
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttClientDisconnectOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
// Sending the DISCONNECT may fail due to connection issues. The resulting exception
|
||||
// must be throw to let the caller know that the disconnect is not a clean one.
|
||||
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
await Send(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await Send(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await DisconnectCoreAsync(null, null, null, clientWasConnected).ConfigureAwait(false);
|
||||
await DisconnectCore(null, null, null, clientWasConnected).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
||||
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
|
||||
await Request<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,6 +265,11 @@ namespace MQTTnet.Client
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttApplicationMessageValidator.ThrowIfNotSupported(applicationMessage, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
@@ -249,11 +280,11 @@ namespace MQTTnet.Client
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
return PublishAtLeastOnceAsync(publishPacket, cancellationToken);
|
||||
return PublishAtLeastOnce(publishPacket, cancellationToken);
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
return PublishExactlyOnceAsync(publishPacket, cancellationToken);
|
||||
return PublishExactlyOnce(publishPacket, cancellationToken);
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -281,7 +312,7 @@ namespace MQTTnet.Client
|
||||
UserProperties = data.UserProperties
|
||||
};
|
||||
|
||||
return SendAsync(authPacket, cancellationToken);
|
||||
return Send(authPacket, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default)
|
||||
@@ -298,13 +329,29 @@ namespace MQTTnet.Client
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientSubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttSubAckPacket subAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
subAckPacket = await Request<MqttSubAckPacket>(subscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientSubscribeResultFactory.Create(subscribePacket, subAckPacket);
|
||||
return MqttClientResultFactory.SubscribeResult.Create(subscribePacket, subAckPacket);
|
||||
}
|
||||
|
||||
public async Task<MqttClientUnsubscribeResult> UnsubscribeAsync(MqttClientUnsubscribeOptions options, CancellationToken cancellationToken = default)
|
||||
@@ -314,15 +361,36 @@ namespace MQTTnet.Client
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
foreach (var topicFilter in options.TopicFilters)
|
||||
{
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicFilter);
|
||||
}
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotConnected();
|
||||
|
||||
if (Options.ValidateFeatures)
|
||||
{
|
||||
MqttClientUnsubscribeOptionsValidator.ThrowIfNotSupported(options, _adapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
}
|
||||
|
||||
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
MqttUnsubAckPacket unsubAckPacket;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var timeout = new CancellationTokenSource(Options.Timeout))
|
||||
{
|
||||
unsubAckPacket = await Request<MqttUnsubAckPacket>(unsubscribePacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return _clientUnsubscribeResultFactory.Create(unsubscribePacket, unsubAckPacket);
|
||||
return MqttClientResultFactory.UnsubscribeResult.Create(unsubscribePacket, unsubAckPacket);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
@@ -346,7 +414,7 @@ namespace MQTTnet.Client
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||
return SendAsync(pubAckPacket, cancellationToken);
|
||||
return Send(pubAckPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (eventArgs.PublishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
|
||||
@@ -354,7 +422,7 @@ namespace MQTTnet.Client
|
||||
if (!eventArgs.ProcessingFailed)
|
||||
{
|
||||
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||
return SendAsync(pubRecPacket, cancellationToken);
|
||||
return Send(pubRecPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -365,27 +433,50 @@ namespace MQTTnet.Client
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
|
||||
async Task<MqttClientConnectResult> Authenticate(IMqttChannelAdapter channelAdapter, MqttClientOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
MqttClientConnectResult result;
|
||||
|
||||
try
|
||||
{
|
||||
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var clientConnectResultFactory = new MqttClientConnectResultFactory();
|
||||
result = clientConnectResultFactory.Create(connAckPacket, options.ProtocolVersion);
|
||||
if (receivedPacket is MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
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
|
||||
{
|
||||
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new MqttConnectingFailedException($"Error while authenticating. {exception.Message}", exception, null);
|
||||
}
|
||||
|
||||
if (result.ResultCode != MqttClientConnectResultCode.Success)
|
||||
// This is no feature. It is basically a backward compatibility option and should be removed in the future.
|
||||
// The client should not throw any exception if the transport layer connection was successful and the server
|
||||
// did send a proper ACK packet with a non success response.
|
||||
if (options.ThrowOnNonSuccessfulConnectResponse)
|
||||
{
|
||||
throw new MqttConnectingFailedException($"Connecting with MQTT server failed ({result.ResultCode}).", null, result);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Verbose("Authenticated MQTT connection with server established.");
|
||||
@@ -397,19 +488,19 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Dispose();
|
||||
_backgroundCancellationTokenSource = null;
|
||||
_mqttClientAlive?.Dispose();
|
||||
_mqttClientAlive = null;
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = null;
|
||||
|
||||
_adapter?.Dispose();
|
||||
_adapter = null;
|
||||
|
||||
|
||||
_packetDispatcher?.Dispose();
|
||||
_packetDispatcher = null;
|
||||
}
|
||||
@@ -420,7 +511,31 @@ namespace MQTTnet.Client
|
||||
return (MqttClientConnectionStatus)Interlocked.CompareExchange(ref _connectionStatus, (int)value, (int)comparand);
|
||||
}
|
||||
|
||||
async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
async Task<MqttClientConnectResult> ConnectInternal(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
var backgroundCancellationToken = _mqttClientAlive.Token;
|
||||
|
||||
using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
|
||||
{
|
||||
_logger.Verbose("Trying to connect with server '{0}'", Options.ChannelOptions);
|
||||
await channelAdapter.ConnectAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
_logger.Verbose("Connection with server established");
|
||||
|
||||
_publishPacketReceiverQueue?.Dispose();
|
||||
_publishPacketReceiverQueue = new AsyncQueue<MqttPublishPacket>();
|
||||
|
||||
var connectResult = await Authenticate(channelAdapter, Options, effectiveCancellationToken.Token).ConfigureAwait(false);
|
||||
if (connectResult.ResultCode == MqttClientConnectResultCode.Success)
|
||||
{
|
||||
_publishPacketReceiverTask = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);
|
||||
_packetReceiverTask = Task.Run(() => ReceivePacketsLoop(backgroundCancellationToken), backgroundCancellationToken);
|
||||
}
|
||||
|
||||
return connectResult;
|
||||
}
|
||||
}
|
||||
|
||||
async Task DisconnectCore(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected)
|
||||
{
|
||||
TryInitiateDisconnect();
|
||||
|
||||
@@ -445,11 +560,11 @@ namespace MQTTnet.Client
|
||||
|
||||
try
|
||||
{
|
||||
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
||||
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
||||
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
||||
_packetDispatcher?.Dispose(new MqttClientDisconnectedException(exception));
|
||||
|
||||
var receiverTask = _packetReceiverTask.WaitAsync(sender, _logger);
|
||||
var publishPacketReceiverTask = _publishPacketReceiverTask.WaitAsync(sender, _logger);
|
||||
var keepAliveTask = _keepAlivePacketsSenderTask.WaitAsync(sender, _logger);
|
||||
|
||||
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
|
||||
}
|
||||
@@ -465,28 +580,27 @@ namespace MQTTnet.Client
|
||||
|
||||
_logger.Info("Disconnected.");
|
||||
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs
|
||||
{
|
||||
ClientWasConnected = clientWasConnected,
|
||||
Exception = exception,
|
||||
ConnectResult = connectResult,
|
||||
Reason = _disconnectReason,
|
||||
ReasonString = _disconnectReasonString
|
||||
};
|
||||
var eventArgs = new MqttClientDisconnectedEventArgs(
|
||||
clientWasConnected,
|
||||
connectResult,
|
||||
(MqttClientDisconnectReason)_disconnectReason,
|
||||
_disconnectReasonString,
|
||||
_disconnectUserProperties,
|
||||
exception);
|
||||
|
||||
// This handler must be executed in a new thread because otherwise a dead lock may happen
|
||||
// when trying to reconnect in that handler etc.
|
||||
Task.Run(() => _disconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
|
||||
Task.Run(() => _events.DisconnectedEvent.InvokeAsync(eventArgs)).RunInBackground(_logger);
|
||||
}
|
||||
}
|
||||
|
||||
Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||
Task DisconnectInternal(Task sender, Exception exception, MqttClientConnectResult connectResult)
|
||||
{
|
||||
var clientWasConnected = IsConnected;
|
||||
|
||||
if (!DisconnectIsPendingOrFinished())
|
||||
{
|
||||
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
|
||||
return DisconnectCore(sender, exception, connectResult, clientWasConnected);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
@@ -531,16 +645,26 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessage(MqttPublishPacket publishPacket)
|
||||
{
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||
|
||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
await _events.ApplicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
return eventArgs;
|
||||
}
|
||||
|
||||
Task OnConnected(MqttClientConnectResult connectResult)
|
||||
{
|
||||
if (_events.ConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new MqttClientConnectedEventArgs(connectResult);
|
||||
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
|
||||
{
|
||||
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
||||
@@ -554,13 +678,14 @@ namespace MQTTnet.Client
|
||||
|
||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
_disconnectReason = (MqttClientDisconnectReason)disconnectPacket.ReasonCode;
|
||||
_disconnectReason = (int)disconnectPacket.ReasonCode;
|
||||
_disconnectReasonString = disconnectPacket.ReasonString;
|
||||
_disconnectUserProperties = disconnectPacket.UserProperties;
|
||||
|
||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||
|
||||
return DisconnectInternalAsync(_packetReceiverTask, null, null);
|
||||
return DisconnectInternal(_packetReceiverTask, null, null);
|
||||
}
|
||||
|
||||
async Task ProcessReceivedPublishPackets(CancellationToken cancellationToken)
|
||||
@@ -576,13 +701,16 @@ namespace MQTTnet.Client
|
||||
}
|
||||
|
||||
var publishPacket = publishPacketDequeueResult.Item;
|
||||
var eventArgs = await HandleReceivedApplicationMessageAsync(publishPacket).ConfigureAwait(false);
|
||||
var eventArgs = await HandleReceivedApplicationMessage(publishPacket).ConfigureAwait(false);
|
||||
|
||||
if (eventArgs.AutoAcknowledge)
|
||||
{
|
||||
await eventArgs.AcknowledgeAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
@@ -600,7 +728,7 @@ namespace MQTTnet.Client
|
||||
// The packet is unknown. Probably due to a restart of the client.
|
||||
// So wen send this to the server to trigger a full resend of the message.
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||
return SendAsync(pubRelPacket, cancellationToken);
|
||||
return Send(pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
return CompletedTask.Instance;
|
||||
@@ -609,40 +737,117 @@ namespace MQTTnet.Client
|
||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
return SendAsync(pubCompPacket, cancellationToken);
|
||||
return Send(pubCompPacket, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishAtLeastOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
async Task<MqttClientPublishResult> PublishAtLeastOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubAckPacket = await SendAndReceiveAsync<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubAckPacket);
|
||||
var pubAckPacket = await Request<MqttPubAckPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
return MqttClientResultFactory.PublishResult.Create(pubAckPacket);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishAtMostOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
// No packet identifier is used for QoS 0 [3.3.2.2 Packet Identifier]
|
||||
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
await Send(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(null);
|
||||
return MqttClientResultFactory.PublishResult.Create(null);
|
||||
}
|
||||
|
||||
async Task<MqttClientPublishResult> PublishExactlyOnceAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
async Task<MqttClientPublishResult> PublishExactlyOnce(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||
|
||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
var pubRecPacket = await Request<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||
|
||||
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
var pubCompPacket = await Request<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return _clientPublishResultFactory.Create(pubRecPacket, pubCompPacket);
|
||||
return MqttClientResultFactory.PublishResult.Create(pubRecPacket, pubCompPacket);
|
||||
}
|
||||
|
||||
async Task<TResponsePacket> SendAndReceiveAsync<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||
async Task<MqttPacket> Receive(CancellationToken cancellationToken)
|
||||
{
|
||||
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
|
||||
|
||||
MqttPacket packet;
|
||||
if (packetTask.IsCompleted)
|
||||
{
|
||||
packet = packetTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = await packetTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
async Task ReceivePacketsLoop(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Start receiving packets");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
await DisconnectInternal(_packetReceiverTask, null, null).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await TryProcessReceivedPacket(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Communication error while receiving packets");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Error while receiving packets");
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped receiving packets");
|
||||
}
|
||||
}
|
||||
|
||||
async Task<TResponsePacket> Request<TResponsePacket>(MqttPacket requestPacket, CancellationToken cancellationToken) where TResponsePacket : MqttPacket
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -656,7 +861,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendAsync(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||
await Send(requestPacket, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -680,7 +885,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
Task SendAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
Task Send(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -693,7 +898,7 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
throw new MqttProtocolViolationException(message);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,17 +906,40 @@ namespace MQTTnet.Client
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
ThrowNotConnected();
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowIfOptionsInvalid(MqttClientOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (options.ChannelOptions == null)
|
||||
{
|
||||
throw new ArgumentException("ChannelOptions are not set.");
|
||||
}
|
||||
|
||||
if (options.ValidateFeatures)
|
||||
{
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(options);
|
||||
}
|
||||
}
|
||||
|
||||
static void ThrowNotConnected()
|
||||
{
|
||||
throw new MqttCommunicationException("The client is not connected.");
|
||||
}
|
||||
|
||||
void TryInitiateDisconnect()
|
||||
{
|
||||
lock (_disconnectLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
_backgroundCancellationTokenSource?.Cancel(false);
|
||||
_mqttClientAlive?.Cancel(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -720,7 +948,7 @@ namespace MQTTnet.Client
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryProcessReceivedPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||
async Task TryProcessReceivedPacket(MqttPacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -781,80 +1009,11 @@ namespace MQTTnet.Client
|
||||
|
||||
_packetDispatcher.FailAll(exception);
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
await DisconnectInternal(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
async Task TryReceivePacketsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Verbose("Start receiving packets.");
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
MqttPacket packet;
|
||||
var packetTask = _adapter.ReceivePacketAsync(cancellationToken);
|
||||
|
||||
if (packetTask.IsCompleted)
|
||||
{
|
||||
packet = packetTask.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
packet = await packetTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet == null)
|
||||
{
|
||||
await DisconnectInternalAsync(_packetReceiverTask, null, null).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await TryProcessReceivedPacketAsync(packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (_cleanDisconnectInitiated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (exception is AggregateException aggregateException)
|
||||
{
|
||||
exception = aggregateException.GetBaseException();
|
||||
}
|
||||
|
||||
if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning(exception, "Communication error while receiving packets.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error(exception, "Error while receiving packets.");
|
||||
}
|
||||
|
||||
_packetDispatcher.FailAll(exception);
|
||||
|
||||
await DisconnectInternalAsync(_packetReceiverTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped receiving packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task TrySendKeepAliveMessagesAsync(CancellationToken cancellationToken)
|
||||
async Task TrySendKeepAliveMessages(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -903,43 +1062,12 @@ namespace MQTTnet.Client
|
||||
_logger.Error(exception, "Error exception while sending/receiving keep alive packets.");
|
||||
}
|
||||
|
||||
await DisconnectInternalAsync(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||
await DisconnectInternal(_keepAlivePacketsSenderTask, exception, null).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.Verbose("Stopped sending keep alive packets.");
|
||||
}
|
||||
}
|
||||
|
||||
async Task WaitForTaskAsync(Task task, Task sender)
|
||||
{
|
||||
if (task == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task == sender)
|
||||
{
|
||||
// Return here to avoid deadlocks, but first any eventual exception in the task
|
||||
// must be handled to avoid not getting an unhandled task exception
|
||||
if (!task.IsFaulted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// By accessing the Exception property the exception is considered handled and will
|
||||
// not result in an unhandled task exception later by the finalizer
|
||||
_logger.Warning(task.Exception, "Error while waiting for background task.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,28 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public static class MqttClientExtensions
|
||||
{
|
||||
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||
public static Task DisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null,
|
||||
uint sessionExpiryInterval = 0,
|
||||
List<MqttUserProperty> userProperties = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
return client.DisconnectAsync(
|
||||
new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString
|
||||
},
|
||||
CancellationToken.None);
|
||||
var disconnectOptions = new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = reason,
|
||||
ReasonString = reasonString,
|
||||
SessionExpiryInterval = sessionExpiryInterval,
|
||||
UserProperties = userProperties
|
||||
};
|
||||
|
||||
return client.DisconnectAsync(disconnectOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<MqttClientPublishResult> PublishBinaryAsync(
|
||||
@@ -128,6 +136,29 @@ namespace MQTTnet.Client
|
||||
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
|
||||
}
|
||||
|
||||
public static async Task<bool> TryDisconnectAsync(
|
||||
this IMqttClient client,
|
||||
MqttClientDisconnectOptionsReason reason = MqttClientDisconnectOptionsReason.NormalDisconnection,
|
||||
string reasonString = null)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await client.DisconnectAsync(reason, reasonString).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore all errors.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client == null)
|
||||
|
||||
@@ -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 SslPolicyErrors SslPolicyErrors { 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,23 @@ namespace MQTTnet.Client
|
||||
{
|
||||
public sealed class MqttClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
||||
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
|
||||
/// will close the connection when receiving such packets. If such a service is used this flag must
|
||||
/// be set to _false_.
|
||||
/// </summary>
|
||||
public bool AllowPacketFragmentation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; set; }
|
||||
|
||||
@@ -58,6 +66,10 @@ namespace MQTTnet.Client
|
||||
/// </summary>
|
||||
public TimeSpan KeepAlivePeriod { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum packet size.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint MaximumPacketSize { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; } = MqttProtocolVersion.V311;
|
||||
@@ -65,27 +77,34 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request problem information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request response information.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether an exception should be thrown when the server has sent a non success ACK packet.
|
||||
/// </summary>
|
||||
public bool ThrowOnNonSuccessfulConnectResponse { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout which will be applied at socket level and internal operations.
|
||||
/// The default value is the same as for sockets in .NET in general.
|
||||
@@ -95,6 +114,7 @@ namespace MQTTnet.Client
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum { get; set; }
|
||||
|
||||
@@ -103,8 +123,10 @@ namespace MQTTnet.Client
|
||||
/// client.
|
||||
/// If successful, this means that loop detection will be more effective and that retained messages will be propagated
|
||||
/// correctly.
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect
|
||||
/// properly.
|
||||
/// <remarks>
|
||||
/// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not
|
||||
/// connect properly.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public bool TryPrivate { get; set; } = true;
|
||||
|
||||
@@ -115,28 +137,41 @@ namespace MQTTnet.Client
|
||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||
/// The feature is very similar to the HTTP header concept.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this feature is enabled the client will check if used properties are supported in the selected protocol
|
||||
/// version.
|
||||
/// This feature can be validated if an application message is generated one time but sent via different protocol
|
||||
/// versions.
|
||||
/// Default values are applied if the validation is off and features are not supported.
|
||||
/// </summary>
|
||||
public bool ValidateFeatures { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content type of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the correlation data of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] WillCorrelationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the will delay interval.
|
||||
/// This is the time between the client disconnect and the time the will message will be sent.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillDelayInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message expiry interval of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public uint WillMessageExpiryInterval { get; set; }
|
||||
|
||||
@@ -147,8 +182,9 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the payload format indicator of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; }
|
||||
public MqttPayloadFormatIndicator WillPayloadFormatIndicator { get; set; } = MqttPayloadFormatIndicator.Unspecified;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the QoS level of the will message.
|
||||
@@ -157,6 +193,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response topic of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string WillResponseTopic { get; set; }
|
||||
|
||||
@@ -172,6 +209,7 @@ namespace MQTTnet.Client
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties of the will message.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> WillUserProperties { get; set; }
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// 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.
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,10 +17,14 @@ namespace MQTTnet.Client
|
||||
public sealed class MqttClientOptionsBuilder
|
||||
{
|
||||
readonly MqttClientOptions _options = new MqttClientOptions();
|
||||
MqttClientWebSocketProxyOptions _proxyOptions;
|
||||
|
||||
[Obsolete] MqttClientWebSocketProxyOptions _proxyOptions;
|
||||
|
||||
MqttClientTcpOptions _tcpOptions;
|
||||
MqttClientOptionsBuilderTlsParameters _tlsParameters;
|
||||
MqttClientTlsOptions _tlsOptions;
|
||||
|
||||
[Obsolete] MqttClientOptionsBuilderTlsParameters _tlsParameters;
|
||||
|
||||
MqttClientWebSocketOptions _webSocketOptions;
|
||||
|
||||
public MqttClientOptions Build()
|
||||
@@ -28,11 +34,17 @@ namespace MQTTnet.Client
|
||||
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?.UseTls == true)
|
||||
{
|
||||
var tlsOptions = new MqttClientTlsOptions
|
||||
tlsOptions = new MqttClientTlsOptions
|
||||
{
|
||||
UseTls = true,
|
||||
SslProtocol = _tlsParameters.SslProtocol,
|
||||
@@ -40,28 +52,23 @@ namespace MQTTnet.Client
|
||||
CertificateValidationHandler = _tlsParameters.CertificateValidationHandler,
|
||||
IgnoreCertificateChainErrors = _tlsParameters.IgnoreCertificateChainErrors,
|
||||
IgnoreCertificateRevocationErrors = _tlsParameters.IgnoreCertificateRevocationErrors,
|
||||
#if WINDOWS_UWP
|
||||
Certificates = _tlsParameters.Certificates?.Select(c => c.ToArray()).ToList(),
|
||||
#else
|
||||
Certificates = _tlsParameters.Certificates?.ToList(),
|
||||
#endif
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
ClientCertificatesProvider = _tlsParameters.CertificatesProvider,
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
ApplicationProtocols = _tlsParameters.ApplicationProtocols,
|
||||
#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 (_webSocketOptions == null)
|
||||
@@ -74,6 +81,8 @@ namespace MQTTnet.Client
|
||||
|
||||
_options.ChannelOptions = (IMqttClientChannelOptions)_tcpOptions ?? _webSocketOptions;
|
||||
|
||||
MqttClientOptionsValidator.ThrowIfNotSupported(_options);
|
||||
|
||||
return _options;
|
||||
}
|
||||
|
||||
@@ -84,18 +93,31 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
|
||||
/// </summary>
|
||||
public MqttClientOptionsBuilder WithCleanSession(bool value = true)
|
||||
{
|
||||
_options.CleanSession = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean start is used in MQTT versions 5.0.0 and higher. It is the same as setting "CleanSession".
|
||||
/// </summary>
|
||||
public MqttClientOptionsBuilder WithCleanStart(bool value = true)
|
||||
{
|
||||
_options.CleanSession = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithClientId(string value)
|
||||
{
|
||||
_options.ClientId = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithConnectionUri(Uri uri)
|
||||
{
|
||||
if (uri == null)
|
||||
@@ -112,12 +134,16 @@ namespace MQTTnet.Client
|
||||
break;
|
||||
|
||||
case "mqtts":
|
||||
WithTcpServer(uri.Host, port).WithTls();
|
||||
WithTcpServer(uri.Host, port)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
});
|
||||
break;
|
||||
|
||||
case "ws":
|
||||
case "wss":
|
||||
WithWebSocketServer(uri.ToString());
|
||||
WithWebSocketServer(o => o.WithUri(uri.ToString()));
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -135,6 +161,7 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithTcpServer(... configure) or WithWebSocketServer(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithConnectionUri(string uri)
|
||||
{
|
||||
return WithConnectionUri(new Uri(uri, UriKind.Absolute));
|
||||
@@ -186,6 +213,28 @@ namespace MQTTnet.Client
|
||||
return WithKeepAlivePeriod(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
||||
/// or WebSocket frames etc. Unfortunately not all brokers (like Amazon Web Services (AWS)) do support this feature and
|
||||
/// will close the connection when receiving such packets. If such a service is used this flag must
|
||||
/// be set to _true_.
|
||||
/// </summary>
|
||||
public MqttClientOptionsBuilder WithoutPacketFragmentation()
|
||||
{
|
||||
_options.AllowPacketFragmentation = false;
|
||||
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)
|
||||
{
|
||||
if (value == MqttProtocolVersion.Unknown)
|
||||
@@ -197,6 +246,7 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithProxy(
|
||||
string address,
|
||||
string username = null,
|
||||
@@ -218,6 +268,7 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithProxy(Action<MqttClientWebSocketProxyOptions> optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder == null)
|
||||
@@ -264,7 +315,7 @@ namespace MQTTnet.Client
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithTcpServer(Action<MqttClientTcpOptions> optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder == null)
|
||||
@@ -288,17 +339,20 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters)
|
||||
{
|
||||
_tlsParameters = parameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithTls()
|
||||
{
|
||||
return WithTls(new MqttClientOptionsBuilderTlsParameters { UseTls = true });
|
||||
}
|
||||
|
||||
[Obsolete("Use WithTlsOptions(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithTls(Action<MqttClientOptionsBuilderTlsParameters> optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder == null)
|
||||
@@ -315,6 +369,26 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
_options.TopicAliasMaximum = topicAliasMaximum;
|
||||
@@ -337,16 +411,6 @@ namespace MQTTnet.Client
|
||||
|
||||
public MqttClientOptionsBuilder WithUserProperty(string name, string value)
|
||||
{
|
||||
if (name is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
@@ -356,6 +420,7 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
[Obsolete("Use WithWebSocketServer(... configure) instead.")]
|
||||
public MqttClientOptionsBuilder WithWebSocketServer(string uri, MqttClientOptionsBuilderWebSocketParameters parameters = null)
|
||||
{
|
||||
_webSocketOptions = new MqttClientWebSocketOptions
|
||||
@@ -368,6 +433,21 @@ namespace MQTTnet.Client
|
||||
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)
|
||||
{
|
||||
if (optionsBuilder == null)
|
||||
@@ -381,38 +461,74 @@ namespace MQTTnet.Client
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
||||
{
|
||||
_options.WillContentType = willContentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
|
||||
{
|
||||
_options.WillCorrelationData = willCorrelationData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillDelayInterval(uint willDelayInterval)
|
||||
{
|
||||
_options.WillDelayInterval = willDelayInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillMessageExpiryInterval(uint willMessageExpiryInterval)
|
||||
{
|
||||
_options.WillMessageExpiryInterval = willMessageExpiryInterval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayload(byte[] willPayload)
|
||||
{
|
||||
_options.WillPayload = willPayload;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayload(ArraySegment<byte> willPayload)
|
||||
{
|
||||
if (willPayload.Count == 0)
|
||||
{
|
||||
_options.WillPayload = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
_options.WillPayload = willPayload.ToArray();
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayload(string willPayload)
|
||||
{
|
||||
if (string.IsNullOrEmpty(willPayload))
|
||||
{
|
||||
return WithWillPayload((byte[])null);
|
||||
}
|
||||
|
||||
|
||||
_options.WillPayload = Encoding.UTF8.GetBytes(willPayload);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||
{
|
||||
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel willQualityOfServiceLevel)
|
||||
{
|
||||
_options.WillQualityOfServiceLevel = willQualityOfServiceLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
||||
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||
{
|
||||
_options.WillTopic = willTopic;
|
||||
_options.WillResponseTopic = willResponseTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -421,38 +537,20 @@ namespace MQTTnet.Client
|
||||
_options.WillRetain = willRetain;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillContentType(string willContentType)
|
||||
|
||||
public MqttClientOptionsBuilder WithWillTopic(string willTopic)
|
||||
{
|
||||
_options.WillContentType = willContentType;
|
||||
_options.WillTopic = willTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillCorrelationData(byte[] willCorrelationData)
|
||||
{
|
||||
_options.WillCorrelationData = willCorrelationData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillResponseTopic(string willResponseTopic)
|
||||
{
|
||||
_options.WillResponseTopic = willResponseTopic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithWillPayloadFormatIndicator(MqttPayloadFormatIndicator willPayloadFormatIndicator)
|
||||
{
|
||||
_options.WillPayloadFormatIndicator = willPayloadFormatIndicator;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttClientOptionsBuilder WithWillUserProperty(string name, string value)
|
||||
{
|
||||
if (_options.WillUserProperties == null)
|
||||
{
|
||||
_options.WillUserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
|
||||
_options.WillUserProperties.Add(new MqttUserProperty(name, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Security;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
[Obsolete("Use methods from MqttClientOptionsBuilder instead.")]
|
||||
public sealed class MqttClientOptionsBuilderTlsParameters
|
||||
{
|
||||
IEnumerable<System.Security.Cryptography.X509Certificates.X509Certificate> _obsoleteCertificates;
|
||||
|
||||
public bool UseTls { get; set; }
|
||||
|
||||
|
||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
||||
|
||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||
#if NET48 || NETCOREAPP3_1_OR_GREATER
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||
#else
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||
@@ -25,17 +26,36 @@ namespace MQTTnet.Client
|
||||
#if WINDOWS_UWP
|
||||
public IEnumerable<IEnumerable<byte>> Certificates { get; set; }
|
||||
#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
|
||||
|
||||
#if NETCOREAPP3_1 || NET5_0_OR_GREATER
|
||||
public List<SslApplicationProtocol> ApplicationProtocols { get;set; }
|
||||
#if NETCOREAPP3_1_OR_GREATER
|
||||
public List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get; set; }
|
||||
#endif
|
||||
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
public bool AllowUntrustedCertificates { get; set; }
|
||||
|
||||
public bool IgnoreCertificateChainErrors { 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.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Client
|
||||
{
|
||||
[Obsolete("Use dedicated methods in MqttClientOptionsBuilder.")]
|
||||
public class MqttClientOptionsBuilderWebSocketParameters
|
||||
{
|
||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user