mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 00:15:15 +08:00
Fix support for enhanced authentication (#2132)
* Implement enhanced authentication for client and server. * Update samples * Add abstraction layer * Merge master * Use dedicated class for public API. * Update release notes * Rename methods to fit naming scheme * Fix unit tests and samples
This commit is contained in:
@@ -8,431 +8,17 @@
|
||||
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Samples.Helpers;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
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 MqttClientFactory();
|
||||
|
||||
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()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker.
|
||||
*
|
||||
* Always dispose the client when it is no longer used.
|
||||
* The default version of MQTT is 3.1.1.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Timeout()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
|
||||
try
|
||||
{
|
||||
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_MQTTv5()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_TLS_1_2()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Connect_Client_With_TLS_Encryption()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.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.
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 MqttClientFactory();
|
||||
|
||||
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()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a clean disconnect which will not
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||
// without sending a DISCONNECT packet to the server.
|
||||
mqttClient.Dispose();
|
||||
}
|
||||
|
||||
public static async Task Inspect_Certificate_Validation_Errors()
|
||||
{
|
||||
/*
|
||||
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
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();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using (var timeout = new CancellationTokenSource(5000))
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Ping_Server()
|
||||
{
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task Reconnect_Using_Event()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses one of the events from the client.
|
||||
* This approach has a risk of dead locks! Consider using the timer approach (see sample).
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
{
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClient.Options);
|
||||
}
|
||||
};
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reconnect_Using_Timer()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses a custom Task/Thread which will monitor the connection status.
|
||||
* This is the recommended way but requires more custom code!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Press <Enter> to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task ConnectTls_WithCaFile()
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
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
|
||||
@@ -460,4 +46,477 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
|
||||
-----END CERTIFICATE-----
|
||||
";
|
||||
|
||||
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 afterward.
|
||||
*
|
||||
* See sample _Connect_Client_ for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
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()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker.
|
||||
*
|
||||
* Always dispose the client when it is no longer used.
|
||||
* The default version of MQTT is 3.1.1.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
// Use builder classes where possible in this project.
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
// This will throw an exception if the server is not available.
|
||||
// The result from this message returns additional data which was sent
|
||||
// from the server. Please refer to the MQTT protocol specification for details.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
|
||||
// Send a clean disconnect to the server by calling _DisconnectAsync_. Without this the TCP connection
|
||||
// gets dropped and the server will handle this as a non clean disconnect (see MQTT spec for details).
|
||||
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
|
||||
|
||||
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_Enhanced_Authentication()
|
||||
{
|
||||
/*
|
||||
* This sample uses enhanced authentication (Kerberos) when creating the connection.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Server part...
|
||||
*/
|
||||
|
||||
var mqttServerFactory = new MqttServerFactory();
|
||||
|
||||
var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||
var server = mqttServerFactory.CreateMqttServer(serverOptions);
|
||||
|
||||
server.ValidatingConnectionAsync += async args =>
|
||||
{
|
||||
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||
{
|
||||
var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
var authOptions = mqttServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||
|
||||
result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from client: {Encoding.UTF8.GetString(result.AuthenticationData)}");
|
||||
|
||||
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||
|
||||
// Authentication DONE!
|
||||
args.ReasonCode = MqttConnectReasonCode.Success; // Also the default!
|
||||
}
|
||||
else
|
||||
{
|
||||
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||
}
|
||||
};
|
||||
|
||||
await server.StartAsync();
|
||||
|
||||
/*
|
||||
* Client part...
|
||||
*/
|
||||
|
||||
var mqttClientFactory = new MqttClientFactory();
|
||||
|
||||
// Use Kerberos sample from the MQTT RFC.
|
||||
var kerberosAuthenticationHandler = new SampleClientKerberosAuthenticationHandler();
|
||||
|
||||
var clientOptions = mqttClientFactory.CreateClientOptionsBuilder()
|
||||
.WithTcpServer("localhost")
|
||||
.WithProtocolVersion(MqttProtocolVersion.V500)
|
||||
.WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler)
|
||||
.WithEnhancedAuthentication("GS2-KRB5")
|
||||
.Build();
|
||||
|
||||
var client = mqttClientFactory.CreateMqttClient();
|
||||
|
||||
var result = await client.ConnectAsync(clientOptions);
|
||||
|
||||
Console.WriteLine($"Client connect result: {result.ResultCode}");
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_MQTTv5()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_1_2()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
// The used public broker sometimes has invalid certificates. This sample accepts all
|
||||
// certificates. This should not be used in live environments.
|
||||
o.WithCertificateValidationHandler(_ => true);
|
||||
|
||||
// The default value is determined by the OS. Set manually to force version.
|
||||
o.WithSslProtocols(SslProtocols.Tls12);
|
||||
})
|
||||
.Build();
|
||||
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_Encryption()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
||||
.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.
|
||||
_ => true))
|
||||
.Build();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_TLS_With_CA_File()
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var 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);
|
||||
}
|
||||
|
||||
public static async Task Connect_Using_WebSockets()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithWebSocketServer(o => o.WithUri("broker.hivemq.com:8000/mqtt")).Build();
|
||||
|
||||
var response = await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
|
||||
response.DumpToConsole();
|
||||
}
|
||||
|
||||
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 MqttClientFactory();
|
||||
|
||||
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()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server with sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a clean disconnect which will not
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
|
||||
// Using a reason code requires MQTT version 5.0.0!
|
||||
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
|
||||
}
|
||||
|
||||
public static async Task Disconnect_Non_Clean()
|
||||
{
|
||||
/*
|
||||
* This sample disconnects from the server without sending a DISCONNECT packet.
|
||||
* This way of disconnecting is treated as a non-clean disconnect which will
|
||||
* trigger sending the last will etc.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
var mqttClient = mqttFactory.CreateMqttClient();
|
||||
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
|
||||
// without sending a DISCONNECT packet to the server.
|
||||
mqttClient.Dispose();
|
||||
}
|
||||
|
||||
public static async Task Inspect_Certificate_Validation_Errors()
|
||||
{
|
||||
/*
|
||||
* This sample prints the certificate information while connection. This data can be used to decide whether a connection is secure or not
|
||||
* including the reason for that status.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
|
||||
.WithTlsOptions(
|
||||
o =>
|
||||
{
|
||||
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();
|
||||
|
||||
// In MQTTv5 the response contains much more information.
|
||||
using var timeout = new CancellationTokenSource(5000);
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
|
||||
}
|
||||
|
||||
public static async Task Ping_Server()
|
||||
{
|
||||
/*
|
||||
* This sample sends a PINGREQ packet to the server and waits for a reply.
|
||||
*
|
||||
* This is only supported in MQTTv5.0.0+.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// This will throw an exception if the server does not reply.
|
||||
await mqttClient.PingAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine("The MQTT server replied to the ping request.");
|
||||
}
|
||||
|
||||
public static async Task Reconnect_Using_Event()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses one of the events from the client.
|
||||
* This approach has a risk of deadlocks! Consider using the timer approach (see sample).
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
mqttClient.DisconnectedAsync += async e =>
|
||||
{
|
||||
if (e.ClientWasConnected)
|
||||
{
|
||||
// Use the current options as the new options.
|
||||
await mqttClient.ConnectAsync(mqttClient.Options);
|
||||
}
|
||||
};
|
||||
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static void Reconnect_Using_Timer()
|
||||
{
|
||||
/*
|
||||
* This sample shows how to reconnect when the connection was dropped.
|
||||
* This approach uses a custom Task/Thread which will monitor the connection status.
|
||||
* This is the recommended way but requires more custom code!
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
|
||||
|
||||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
// User proper cancellation and no while(true).
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This code will also do the very first connect! So no call to _ConnectAsync_ is required in the first place.
|
||||
if (!await mqttClient.TryPingAsync())
|
||||
{
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
|
||||
|
||||
// Subscribe to topics when session is clean etc.
|
||||
Console.WriteLine("The MQTT client is connected.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle the exception properly (logging etc.).
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Check the connection state every 5 seconds and perform a reconnect if required.
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Console.WriteLine("Press <Enter> to exit");
|
||||
Console.ReadLine();
|
||||
}
|
||||
|
||||
public static async Task Timeout()
|
||||
{
|
||||
/*
|
||||
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout.
|
||||
*
|
||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||
*/
|
||||
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
|
||||
|
||||
try
|
||||
{
|
||||
using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
|
||||
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Console.WriteLine("Timeout while connecting.");
|
||||
}
|
||||
}
|
||||
|
||||
class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
|
||||
{
|
||||
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
|
||||
{
|
||||
throw new InvalidOperationException("Wrong authentication method");
|
||||
}
|
||||
|
||||
var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = "initial context token"u8.ToArray()
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions);
|
||||
|
||||
var response = await eventArgs.ReceiveAsync(CancellationToken.None);
|
||||
|
||||
Console.WriteLine($"Received AUTH data from server: {Encoding.UTF8.GetString(response.AuthenticationData)}");
|
||||
|
||||
// No further data is required, but we have to fulfil the exchange.
|
||||
sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = []
|
||||
};
|
||||
|
||||
await eventArgs.SendAsync(sendOptions, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationOptions
|
||||
{
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
// 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.Text;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationOptionsFactory
|
||||
{
|
||||
readonly ExchangeEnhancedAuthenticationOptions _options = new();
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptions Build()
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithAuthenticationData(byte[] authenticationData)
|
||||
{
|
||||
_options.AuthenticationData = authenticationData;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithAuthenticationData(string authenticationData)
|
||||
{
|
||||
if (authenticationData == null)
|
||||
{
|
||||
_options.AuthenticationData = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_options.AuthenticationData = Encoding.UTF8.GetBytes(authenticationData);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithReasonString(string reasonString)
|
||||
{
|
||||
_options.ReasonString = reasonString;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperties(List<MqttUserProperty> userProperties)
|
||||
{
|
||||
_options.UserProperties = userProperties;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, string value)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
if (_options.UserProperties == null)
|
||||
{
|
||||
_options.UserProperties = new List<MqttUserProperty>();
|
||||
}
|
||||
|
||||
_options.UserProperties.Add(new MqttUserProperty(name, value));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public sealed class ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; set; }
|
||||
|
||||
public byte[] AuthenticationData { get; set; }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
public static class ExchangeEnhancedAuthenticationResultFactory
|
||||
{
|
||||
public static ExchangeEnhancedAuthenticationResult Create(MqttAuthPacket authPacket)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(authPacket);
|
||||
|
||||
return new ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
AuthenticationData = authPacket.AuthenticationData,
|
||||
|
||||
ReasonString = authPacket.ReasonString,
|
||||
UserProperties = authPacket.UserProperties
|
||||
};
|
||||
}
|
||||
|
||||
public static ExchangeEnhancedAuthenticationResult Create(MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(disconnectPacket);
|
||||
|
||||
return new ExchangeEnhancedAuthenticationResult
|
||||
{
|
||||
AuthenticationData = null,
|
||||
ReasonString = disconnectPacket.ReasonString,
|
||||
UserProperties = disconnectPacket.UserProperties
|
||||
|
||||
// SessionExpiryInterval makes no sense because the connection is not yet made!
|
||||
// ServerReferences makes no sense when the client initiated a DISCONNECT!
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,193 +2,243 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
namespace MQTTnet.Server;
|
||||
|
||||
public sealed class ValidatingConnectionEventArgs : EventArgs
|
||||
{
|
||||
public sealed class ValidatingConnectionEventArgs : EventArgs
|
||||
{
|
||||
readonly MqttConnectPacket _connectPacket;
|
||||
readonly MqttConnectPacket _connectPacket;
|
||||
|
||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems)
|
||||
public ValidatingConnectionEventArgs(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary sessionItems, CancellationToken cancellationToken)
|
||||
{
|
||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assigned client identifier.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string AssignedClientIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||
/// TCP or WebSockets) or a custom implementation.
|
||||
/// </summary>
|
||||
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether clean sessions are used or not.
|
||||
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
||||
/// persistent connection.
|
||||
/// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for
|
||||
/// the client.
|
||||
/// This mode is ideal when the client only publishes messages.
|
||||
/// It can also connect as a durable client using a persistent connection.
|
||||
/// In this mode, the broker will store subscription information, and undelivered messages for the client.
|
||||
/// </summary>
|
||||
public bool? CleanSession => _connectPacket.CleanSession;
|
||||
|
||||
public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client identifier.
|
||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||
/// </summary>
|
||||
public string ClientId => _connectPacket.ClientId;
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
public bool IsSecureConnection => ChannelAdapter.IsSecureConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keep alive period.
|
||||
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
||||
/// If no data flows over an open connection for a certain time period then the client will generate a PINGREQ and
|
||||
/// expect to receive a PINGRESP from the broker.
|
||||
/// This message exchange confirms that the connection is open and working.
|
||||
/// This period is known as the keep alive period.
|
||||
/// </summary>
|
||||
public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
||||
|
||||
/// <summary>
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
|
||||
|
||||
public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array);
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||
|
||||
public byte[] RawPassword => _connectPacket.Password;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is
|
||||
/// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be
|
||||
/// converted properly.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
|
||||
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the received messages.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum => _connectPacket.ReceiveMaximum;
|
||||
|
||||
public EndPoint RemoteEndPoint => ChannelAdapter.RemoteEndPoint;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request problem information.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation => _connectPacket.RequestProblemInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request response information.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation => _connectPacket.RequestResponseInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response authentication data.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] ResponseAuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> ResponseUserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send
|
||||
/// a different server address to the client.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ServerReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// A value of 0 means no expiation.
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval => _connectPacket.SessionExpiryInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
public IDictionary SessionItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
||||
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties => _connectPacket.UserProperties;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delay interval for the will message.
|
||||
/// This is the time between the client disconnect and the time the will message will be sent.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
||||
|
||||
public async Task<ExchangeEnhancedAuthenticationResult> ExchangeEnhancedAuthenticationAsync(
|
||||
ExchangeEnhancedAuthenticationOptions options,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var requestAuthPacket = new MqttAuthPacket
|
||||
{
|
||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
// From RFC: If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
|
||||
// and any successful CONNACK packet MUST include an Authentication Method Property with the same value as in the CONNECT packet [MQTT-4.12.0-5].
|
||||
AuthenticationMethod = AuthenticationMethod,
|
||||
|
||||
// The reason code will stay at continue all the time when connecting. The server will respond with the
|
||||
// CONNACK packet when authentication is done!
|
||||
ReasonCode = MqttAuthenticateReasonCode.ContinueAuthentication,
|
||||
|
||||
AuthenticationData = options.AuthenticationData,
|
||||
ReasonString = options.ReasonString,
|
||||
UserProperties = options.UserProperties
|
||||
};
|
||||
|
||||
await ChannelAdapter.SendPacketAsync(requestAuthPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var responsePacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (responsePacket == null)
|
||||
{
|
||||
throw new MqttCommunicationException("The client closed the connection.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the assigned client identifier.
|
||||
/// MQTTv5 only.
|
||||
/// </summary>
|
||||
public string AssignedClientIdentifier { get; set; }
|
||||
if (responsePacket is MqttAuthPacket responseAuthPacket)
|
||||
{
|
||||
if (!string.Equals(AuthenticationMethod, responseAuthPacket.AuthenticationMethod, StringComparison.Ordinal))
|
||||
{
|
||||
throw new MqttProtocolViolationException("The authentication method cannot change while authenticating the client.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
|
||||
return ExchangeEnhancedAuthenticationResultFactory.Create(responseAuthPacket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication method.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
|
||||
if (responsePacket is MqttDisconnectPacket disconnectPacket)
|
||||
{
|
||||
return ExchangeEnhancedAuthenticationResultFactory.Create(disconnectPacket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel adapter. This can be a _MqttConnectionContext_ (used in ASP.NET), a _MqttChannelAdapter_ (used for
|
||||
/// TCP or WebSockets) or a custom implementation.
|
||||
/// </summary>
|
||||
public IMqttChannelAdapter ChannelAdapter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether clean sessions are used or not.
|
||||
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
||||
/// persistent connection.
|
||||
/// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for
|
||||
/// the client.
|
||||
/// This mode is ideal when the client only publishes messages.
|
||||
/// It can also connect as a durable client using a persistent connection.
|
||||
/// In this mode, the broker will store subscription information, and undelivered messages for the client.
|
||||
/// </summary>
|
||||
public bool? CleanSession => _connectPacket.CleanSession;
|
||||
|
||||
public X509Certificate2 ClientCertificate => ChannelAdapter.ClientCertificate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the client identifier.
|
||||
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
|
||||
/// </summary>
|
||||
public string ClientId => _connectPacket.ClientId;
|
||||
|
||||
public EndPoint RemoteEndPoint => ChannelAdapter.RemoteEndPoint;
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
public bool IsSecureConnection => ChannelAdapter.IsSecureConnection;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keep alive period.
|
||||
/// The connection is normally left open by the client so that is can send and receive data at any time.
|
||||
/// If no data flows over an open connection for a certain time period then the client will generate a PINGREQ and
|
||||
/// expect to receive a PINGRESP from the broker.
|
||||
/// This message exchange confirms that the connection is open and working.
|
||||
/// This period is known as the keep alive period.
|
||||
/// </summary>
|
||||
public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
|
||||
|
||||
/// <summary>
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
|
||||
|
||||
public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array);
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
|
||||
|
||||
public byte[] RawPassword => _connectPacket.Password;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is
|
||||
/// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be
|
||||
/// converted properly.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
|
||||
|
||||
public string ReasonString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum => _connectPacket.ReceiveMaximum;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request problem information.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestProblemInformation => _connectPacket.RequestProblemInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request response information.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public bool RequestResponseInformation => _connectPacket.RequestResponseInformation;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response authentication data.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public byte[] ResponseAuthenticationData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the response user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> ResponseUserProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send
|
||||
/// a different server address to the client.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public string ServerReference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the session expiry interval.
|
||||
/// The time after a session expires when it's not actively used.
|
||||
/// A value of 0 means no expiation.
|
||||
/// </summary>
|
||||
public uint SessionExpiryInterval => _connectPacket.SessionExpiryInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
public IDictionary SessionItems { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the topic alias maximum.
|
||||
/// This gives the maximum length of the topic alias.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public ushort TopicAliasMaximum => _connectPacket.TopicAliasMaximum;
|
||||
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties => _connectPacket.UserProperties;
|
||||
|
||||
/// <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.
|
||||
/// A value of 0 indicates that the value is not used.
|
||||
/// </summary>
|
||||
public uint WillDelayInterval => _connectPacket.WillDelayInterval;
|
||||
throw new MqttProtocolViolationException("Received other packet than AUTH while authenticating.");
|
||||
}
|
||||
}
|
||||
@@ -364,7 +364,7 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
return;
|
||||
}
|
||||
|
||||
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
|
||||
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter, cancellationToken).ConfigureAwait(false);
|
||||
var connAckPacket = MqttConnAckPacketFactory.Create(validatingConnectionEventArgs);
|
||||
|
||||
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
|
||||
@@ -740,11 +740,11 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
}
|
||||
}
|
||||
|
||||
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
|
||||
async Task<ValidatingConnectionEventArgs> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Load session items from persisted sessions in the future.
|
||||
var sessionItems = new ConcurrentDictionary<object, object>();
|
||||
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems);
|
||||
var eventArgs = new ValidatingConnectionEventArgs(connectPacket, channelAdapter, sessionItems, cancellationToken);
|
||||
await _eventContainer.ValidatingConnectionEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
|
||||
// Check the client ID and set a random one if supported.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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.ObjectModel;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Internal;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
using MQTTnet.Server.Internal.Adapter;
|
||||
|
||||
namespace MQTTnet.Server;
|
||||
@@ -32,6 +33,11 @@ public sealed class MqttServerFactory
|
||||
return new MqttApplicationMessageBuilder();
|
||||
}
|
||||
|
||||
public ExchangeEnhancedAuthenticationOptionsFactory CreateExchangeExtendedAuthenticationOptionsBuilder()
|
||||
{
|
||||
return new ExchangeEnhancedAuthenticationOptionsFactory();
|
||||
}
|
||||
|
||||
public MqttServer CreateMqttServer(MqttServerOptions options)
|
||||
{
|
||||
return CreateMqttServer(options, DefaultLogger);
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.EnhancedAuthentication;
|
||||
|
||||
namespace MQTTnet.Tests.Clients.MqttClient
|
||||
{
|
||||
@@ -24,10 +24,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
||||
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
|
||||
{
|
||||
var client = new MqttClientFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
|
||||
}
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).Build(), timeout.Token);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -35,10 +33,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
||||
public async Task Connect_To_Invalid_Server_Wrong_IP()
|
||||
{
|
||||
var client = new MqttClientFactory().CreateMqttClient();
|
||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||
{
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
}
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build(), timeout.Token);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -53,179 +49,229 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
||||
public async Task ConnectTimeout_Throws_Exception()
|
||||
{
|
||||
var factory = new MqttClientFactory();
|
||||
using (var client = factory.CreateMqttClient())
|
||||
using var client = factory.CreateMqttClient();
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
{
|
||||
var disconnectHandlerCalled = false;
|
||||
try
|
||||
client.DisconnectedAsync += _ =>
|
||||
{
|
||||
client.DisconnectedAsync += args =>
|
||||
{
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
disconnectHandlerCalled = true;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build());
|
||||
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").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);
|
||||
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))
|
||||
using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
await LongTestDelay();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
}
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Disconnect_Clean_With_Custom_Reason()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||
using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
|
||||
var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.MessageRateTooHigh).Build();
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
await LongTestDelay();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
Assert.AreEqual(MqttDisconnectReasonCode.MessageRateTooHigh, eventArgs.ReasonCode);
|
||||
}
|
||||
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();
|
||||
using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
ClientDisconnectedEventArgs eventArgs = null;
|
||||
server.ClientDisconnectedAsync += args =>
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
|
||||
var disconnectOptions = testEnvironment.ClientFactory.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);
|
||||
}
|
||||
|
||||
class TestClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
|
||||
{
|
||||
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
|
||||
{
|
||||
eventArgs = args;
|
||||
return CompletedTask.Instance;
|
||||
throw new InvalidOperationException("Wrong authentication method");
|
||||
}
|
||||
|
||||
var sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = "initial context token"u8.ToArray()
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken);
|
||||
|
||||
var disconnectOptions = testEnvironment.ClientFactory.CreateClientDisconnectOptionsBuilder().WithUserProperty("test_name", "test_value").Build();
|
||||
var response = await eventArgs.ReceiveAsync(eventArgs.CancellationToken);
|
||||
|
||||
// Perform a clean disconnect.
|
||||
await client.DisconnectAsync(disconnectOptions);
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(response.AuthenticationData), "reply context token");
|
||||
|
||||
await LongTestDelay();
|
||||
// No further data is required, but we have to fulfil the exchange.
|
||||
sendOptions = new SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
Data = []
|
||||
};
|
||||
|
||||
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);
|
||||
await eventArgs.SendAsync(sendOptions, eventArgs.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Use_Enhanced_Authentication()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
server.ValidatingConnectionAsync += async args =>
|
||||
{
|
||||
if (args.AuthenticationMethod == "GS2-KRB5")
|
||||
{
|
||||
var result = await args.ExchangeEnhancedAuthenticationAsync(new ExchangeEnhancedAuthenticationOptions(), args.CancellationToken);
|
||||
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), "initial context token");
|
||||
|
||||
var authOptions = testEnvironment.ServerFactory.CreateExchangeExtendedAuthenticationOptionsBuilder().WithAuthenticationData("reply context token").Build();
|
||||
|
||||
result = await args.ExchangeEnhancedAuthenticationAsync(authOptions, args.CancellationToken);
|
||||
|
||||
Assert.AreEqual(Encoding.UTF8.GetString(result.AuthenticationData), "");
|
||||
|
||||
args.ResponseAuthenticationData = "outcome of authentication"u8.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
args.ReasonCode = MqttConnectReasonCode.BadAuthenticationMethod;
|
||||
}
|
||||
};
|
||||
|
||||
// Use Kerberos sample from the MQTT RFC.
|
||||
var kerberosAuthenticationHandler = new TestClientKerberosAuthenticationHandler();
|
||||
|
||||
var clientOptions = testEnvironment.CreateDefaultClientOptionsBuilder().WithEnhancedAuthentication("GS2-KRB5").WithEnhancedAuthenticationHandler(kerberosAuthenticationHandler);
|
||||
var client = await testEnvironment.ConnectClient(clientOptions);
|
||||
|
||||
Assert.IsTrue(client.IsConnected);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task No_Unobserved_Exception()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
var options = new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||
|
||||
try
|
||||
{
|
||||
testEnvironment.IgnoreClientLogErrors = true;
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
var options = new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").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();
|
||||
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))
|
||||
using var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500);
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
server.ValidatingConnectionAsync += args =>
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
args.ResponseUserProperties = [new("Property", "Value")];
|
||||
|
||||
server.ValidatingConnectionAsync += args =>
|
||||
{
|
||||
args.ResponseUserProperties = new List<MqttUserProperty>
|
||||
{
|
||||
new MqttUserProperty("Property", "Value")
|
||||
};
|
||||
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
||||
|
||||
args.ReasonCode = MqttConnectReasonCode.QuotaExceeded;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
var client = testEnvironment.CreateClient();
|
||||
|
||||
var client = testEnvironment.CreateClient();
|
||||
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().Build());
|
||||
|
||||
var response = await client.ConnectAsync(testEnvironment.CreateDefaultClientOptionsBuilder().Build());
|
||||
|
||||
Assert.IsNotNull(response);
|
||||
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
||||
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
||||
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
||||
}
|
||||
Assert.IsNotNull(response);
|
||||
Assert.AreEqual(MqttClientConnectResultCode.QuotaExceeded, response.ResultCode);
|
||||
Assert.AreEqual(response.UserProperties[0].Name, "Property");
|
||||
Assert.AreEqual(response.UserProperties[0].Value, "Value");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -234,10 +280,8 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
||||
try
|
||||
{
|
||||
var mqttFactory = new MqttClientFactory();
|
||||
using (var mqttClient = mqttFactory.CreateMqttClient())
|
||||
{
|
||||
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
using var mqttClient = mqttFactory.CreateMqttClient();
|
||||
await mqttClient.SubscribeAsync("test", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
}
|
||||
catch (MqttClientNotConnectedException exception)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
-63
@@ -1,63 +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.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public class MqttExtendedAuthenticationExchangeContext
|
||||
{
|
||||
public MqttExtendedAuthenticationExchangeContext(MqttAuthPacket authPacket, MqttClient client)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(authPacket);
|
||||
|
||||
ReasonCode = authPacket.ReasonCode;
|
||||
ReasonString = authPacket.ReasonString;
|
||||
AuthenticationMethod = authPacket.AuthenticationMethod;
|
||||
AuthenticationData = authPacket.AuthenticationData;
|
||||
UserProperties = authPacket.UserProperties;
|
||||
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public string AuthenticationMethod { get; }
|
||||
|
||||
public MqttClient Client { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public MqttAuthenticateReasonCode ReasonCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public string ReasonString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties { get; }
|
||||
}
|
||||
@@ -2,24 +2,23 @@
|
||||
// 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.Formatter
|
||||
{
|
||||
public struct ReadFixedHeaderResult
|
||||
{
|
||||
public static ReadFixedHeaderResult Canceled { get; } = new ReadFixedHeaderResult
|
||||
{
|
||||
IsCanceled = true
|
||||
};
|
||||
|
||||
public static ReadFixedHeaderResult ConnectionClosed { get; } = new ReadFixedHeaderResult
|
||||
{
|
||||
IsConnectionClosed = true
|
||||
};
|
||||
|
||||
public bool IsCanceled { get; set; }
|
||||
|
||||
public bool IsConnectionClosed { get; set; }
|
||||
namespace MQTTnet.Formatter;
|
||||
|
||||
public MqttFixedHeader FixedHeader { get; set; }
|
||||
}
|
||||
}
|
||||
public struct ReadFixedHeaderResult
|
||||
{
|
||||
public static ReadFixedHeaderResult Canceled { get; } = new()
|
||||
{
|
||||
IsCanceled = true
|
||||
};
|
||||
|
||||
public static ReadFixedHeaderResult ConnectionClosed { get; } = new()
|
||||
{
|
||||
IsConnectionClosed = true
|
||||
};
|
||||
|
||||
public bool IsCanceled { get; set; }
|
||||
|
||||
public bool IsConnectionClosed { get; init; }
|
||||
|
||||
public MqttFixedHeader FixedHeader { get; init; }
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace MQTTnet.Formatter.V5
|
||||
{
|
||||
public sealed class MqttV5PacketDecoder
|
||||
{
|
||||
readonly MqttBufferReader _bufferReader = new MqttBufferReader();
|
||||
readonly MqttBufferReader _bufferReader = new();
|
||||
|
||||
public MqttPacket Decode(ReceivedMqttPacket receivedMqttPacket)
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ public interface IMqttClient : IDisposable
|
||||
|
||||
Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default);
|
||||
|
||||
Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
|
||||
Task SendEnhancedAuthenticationExchangeDataAsync(MqttEnhancedAuthenticationExchangeData data, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<MqttClientSubscribeResult> SubscribeAsync(MqttClientSubscribeOptions options, CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ public sealed class MqttClient : Disposable, IMqttClient
|
||||
}
|
||||
}
|
||||
|
||||
public Task SendExtendedAuthenticationExchangeDataAsync(MqttExtendedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
|
||||
public Task SendEnhancedAuthenticationExchangeDataAsync(MqttEnhancedAuthenticationExchangeData data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
@@ -437,27 +437,30 @@ public sealed class MqttClient : Disposable, IMqttClient
|
||||
var connectPacket = MqttConnectPacketFactory.Create(options);
|
||||
await Send(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
switch (receivedPacket)
|
||||
while (true)
|
||||
{
|
||||
case MqttConnAckPacket connAckPacket:
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var receivedPacket = await Receive(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (receivedPacket is MqttAuthPacket authPacket)
|
||||
{
|
||||
await HandleEnhancedAuthentication(authPacket, cancellationToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (receivedPacket is MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
result = MqttClientResultFactory.ConnectResult.Create(connAckPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion);
|
||||
break;
|
||||
}
|
||||
case MqttAuthPacket _:
|
||||
|
||||
if (receivedPacket != null)
|
||||
{
|
||||
throw new NotSupportedException("Extended authentication handler is not yet supported");
|
||||
}
|
||||
case null:
|
||||
{
|
||||
throw new MqttCommunicationException("Connection closed.");
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException($"Received an unexpected MQTT packet ({receivedPacket}).");
|
||||
throw new MqttProtocolViolationException($"Received other packet than CONNACK or AUTH while connecting ({receivedPacket}).");
|
||||
}
|
||||
|
||||
throw new MqttCommunicationException("Connection closed.");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -470,6 +473,12 @@ public sealed class MqttClient : Disposable, IMqttClient
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task HandleEnhancedAuthentication(MqttAuthPacket authPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var eventArgs = new MqttEnhancedAuthenticationEventArgs(authPacket, _adapter, cancellationToken);
|
||||
await Options.EnhancedAuthenticationHandler.HandleEnhancedAuthenticationAsync(eventArgs);
|
||||
}
|
||||
|
||||
void Cleanup()
|
||||
{
|
||||
try
|
||||
@@ -646,12 +655,26 @@ public sealed class MqttClient : Disposable, IMqttClient
|
||||
return _events.ConnectedEvent.InvokeAsync(eventArgs);
|
||||
}
|
||||
|
||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket)
|
||||
Task ProcessReceivedAuthPacket(MqttAuthPacket authPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var extendedAuthenticationExchangeHandler = Options.ExtendedAuthenticationExchangeHandler;
|
||||
return extendedAuthenticationExchangeHandler != null
|
||||
? extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this))
|
||||
: CompletedTask.Instance;
|
||||
if (Options.EnhancedAuthenticationHandler == null)
|
||||
{
|
||||
// From RFC: If the re-authentication fails, the Client or Server SHOULD send DISCONNECT with an appropriate Reason Code
|
||||
// as described in section 4.13, and MUST close the Network Connection [MQTT-4.12.1-2].
|
||||
//
|
||||
// Since we have no handler there is no chance to fulfil the re-authentication request.
|
||||
_ = DisconnectAsync(new MqttClientDisconnectOptions
|
||||
{
|
||||
Reason = MqttClientDisconnectOptionsReason.ImplementationSpecificError,
|
||||
ReasonString = "Unable to handle AUTH packet"
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
|
||||
var eventArgs = new MqttEnhancedAuthenticationEventArgs(authPacket, _adapter, cancellationToken);
|
||||
return Options.EnhancedAuthenticationHandler.HandleEnhancedAuthenticationAsync(eventArgs);
|
||||
}
|
||||
|
||||
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
|
||||
@@ -959,7 +982,7 @@ public sealed class MqttClient : Disposable, IMqttClient
|
||||
await ProcessReceivedDisconnectPacket(disconnectPacket).ConfigureAwait(false);
|
||||
break;
|
||||
case MqttAuthPacket authPacket:
|
||||
await ProcessReceivedAuthPacket(authPacket).ConfigureAwait(false);
|
||||
await ProcessReceivedAuthPacket(authPacket, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case MqttPingRespPacket _:
|
||||
_packetDispatcher.TryDispatch(packet);
|
||||
|
||||
@@ -99,11 +99,11 @@ public static class MqttClientExtensions
|
||||
return client.ConnectAsync(client.Options, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
|
||||
public static Task SendEnhancedAuthenticationExchangeDataAsync(this IMqttClient client, MqttEnhancedAuthenticationExchangeData data)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
|
||||
return client.SendExtendedAuthenticationExchangeDataAsync(data, CancellationToken.None);
|
||||
return client.SendEnhancedAuthenticationExchangeDataAsync(data, CancellationToken.None);
|
||||
}
|
||||
|
||||
public static Task<MqttClientSubscribeResult> SubscribeAsync(this IMqttClient mqttClient, MqttTopicFilter topicFilter, CancellationToken cancellationToken = default)
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public interface IMqttExtendedAuthenticationExchangeHandler
|
||||
public interface IMqttEnhancedAuthenticationHandler
|
||||
{
|
||||
Task HandleRequestAsync(MqttExtendedAuthenticationExchangeContext context);
|
||||
Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace MQTTnet;
|
||||
public sealed class MqttClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Usually the MQTT packets can be send partially. This is done by using multiple TCP packets
|
||||
/// Usually the MQTT packets can be sent 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_.
|
||||
@@ -38,7 +38,7 @@ public sealed class MqttClientOptions
|
||||
/// Gets or sets a value indicating whether clean sessions are used or not.
|
||||
/// When a client connects to a broker it can connect using either a non persistent connection (clean session) or a
|
||||
/// persistent connection.
|
||||
/// With a non persistent connection the broker doesn't store any subscription information or undelivered messages for
|
||||
/// With a non-persistent connection the broker doesn't store any subscription information or undelivered messages for
|
||||
/// the client.
|
||||
/// This mode is ideal when the client only publishes messages.
|
||||
/// It can also connect as a durable client using a persistent connection.
|
||||
@@ -54,7 +54,12 @@ public sealed class MqttClientOptions
|
||||
|
||||
public IMqttClientCredentialsProvider Credentials { get; set; }
|
||||
|
||||
public IMqttExtendedAuthenticationExchangeHandler ExtendedAuthenticationExchangeHandler { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the handler for AUTH packets.
|
||||
/// This can happen when connecting or at any time while being already connected.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public IMqttEnhancedAuthenticationHandler EnhancedAuthenticationHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keep alive period.
|
||||
@@ -80,7 +85,7 @@ public sealed class MqttClientOptions
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the receive maximum.
|
||||
/// This gives the maximum length of the receive messages.
|
||||
/// This gives the maximum length of the received messages.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
/// </summary>
|
||||
public ushort ReceiveMaximum { get; set; }
|
||||
|
||||
@@ -2,14 +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 MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
@@ -98,13 +98,6 @@ public sealed class MqttClientOptionsBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithAuthentication(string method, byte[] data)
|
||||
{
|
||||
_options.AuthenticationMethod = method;
|
||||
_options.AuthenticationData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clean session is used in MQTT versions below 5.0.0. It is the same as setting "CleanStart".
|
||||
/// </summary>
|
||||
@@ -138,27 +131,19 @@ public sealed class MqttClientOptionsBuilder
|
||||
{
|
||||
case "tcp":
|
||||
case "mqtt":
|
||||
WithTcpServer(uri.Host, port)
|
||||
.WithAddressFamily(AddressFamily.Unspecified)
|
||||
.WithProtocolType(ProtocolType.Tcp)
|
||||
.WithTlsOptions(o => o.UseTls(false));
|
||||
WithTcpServer(uri.Host, port).WithAddressFamily(AddressFamily.Unspecified).WithProtocolType(ProtocolType.Tcp).WithTlsOptions(o => o.UseTls(false));
|
||||
break;
|
||||
|
||||
case "mqtts":
|
||||
WithTcpServer(uri.Host, port)
|
||||
.WithAddressFamily(AddressFamily.Unspecified)
|
||||
.WithProtocolType(ProtocolType.Tcp)
|
||||
.WithTlsOptions(o => o.UseTls(true));
|
||||
WithTcpServer(uri.Host, port).WithAddressFamily(AddressFamily.Unspecified).WithProtocolType(ProtocolType.Tcp).WithTlsOptions(o => o.UseTls());
|
||||
break;
|
||||
|
||||
case "ws":
|
||||
WithWebSocketServer(o => o.WithUri(uri.ToString()))
|
||||
.WithTlsOptions(o => o.UseTls(false));
|
||||
WithWebSocketServer(o => o.WithUri(uri.ToString())).WithTlsOptions(o => o.UseTls(false));
|
||||
break;
|
||||
|
||||
case "wss":
|
||||
WithWebSocketServer(o => o.WithUri(uri.ToString()))
|
||||
.WithTlsOptions(o => o.UseTls(true));
|
||||
WithWebSocketServer(o => o.WithUri(uri.ToString())).WithTlsOptions(o => o.UseTls());
|
||||
break;
|
||||
|
||||
// unix:///path/to/socket
|
||||
@@ -220,9 +205,16 @@ public sealed class MqttClientOptionsBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithExtendedAuthenticationExchangeHandler(IMqttExtendedAuthenticationExchangeHandler handler)
|
||||
public MqttClientOptionsBuilder WithEnhancedAuthentication(string method, byte[] data = null)
|
||||
{
|
||||
_options.ExtendedAuthenticationExchangeHandler = handler;
|
||||
_options.AuthenticationMethod = method;
|
||||
_options.AuthenticationData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttClientOptionsBuilder WithEnhancedAuthenticationHandler(IMqttEnhancedAuthenticationHandler handler)
|
||||
{
|
||||
_options.EnhancedAuthenticationHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,11 @@ public static class MqttClientOptionsValidator
|
||||
{
|
||||
Throw(nameof(options.WillUserProperties));
|
||||
}
|
||||
|
||||
if (options.EnhancedAuthenticationHandler != null)
|
||||
{
|
||||
Throw(nameof(options.EnhancedAuthenticationHandler));
|
||||
}
|
||||
}
|
||||
|
||||
static void Throw(string featureName)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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 MQTTnet.Adapter;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public class MqttEnhancedAuthenticationEventArgs : EventArgs
|
||||
{
|
||||
readonly IMqttChannelAdapter _channelAdapter;
|
||||
readonly MqttAuthPacket _initialAuthPacket;
|
||||
|
||||
public MqttEnhancedAuthenticationEventArgs(MqttAuthPacket initialAuthPacket, IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
_initialAuthPacket = initialAuthPacket ?? throw new ArgumentNullException(nameof(initialAuthPacket));
|
||||
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||
|
||||
CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication data.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public byte[] AuthenticationData => _initialAuthPacket.AuthenticationData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authentication method.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public string AuthenticationMethod => _initialAuthPacket.AuthenticationMethod;
|
||||
|
||||
public CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason code.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public MqttAuthenticateReasonCode ReasonCode => _initialAuthPacket.ReasonCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason string.
|
||||
/// Hint: MQTT 5 feature only.
|
||||
/// </summary>
|
||||
public string ReasonString => _initialAuthPacket.ReasonString;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties.
|
||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||
/// packet.
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public List<MqttUserProperty> UserProperties => _initialAuthPacket.UserProperties;
|
||||
|
||||
public async Task<ReceiveMqttEnhancedAuthenticationDataResult> ReceiveAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var receivedPacket = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (receivedPacket is MqttAuthPacket authPacket)
|
||||
{
|
||||
return new ReceiveMqttEnhancedAuthenticationDataResult
|
||||
{
|
||||
AuthenticationData = authPacket.AuthenticationData,
|
||||
AuthenticationMethod = authPacket.AuthenticationMethod,
|
||||
ReasonString = authPacket.ReasonString,
|
||||
ReasonCode = authPacket.ReasonCode,
|
||||
UserProperties = authPacket.UserProperties
|
||||
};
|
||||
}
|
||||
|
||||
if (receivedPacket is MqttConnAckPacket)
|
||||
{
|
||||
throw new InvalidOperationException("The enhanced authentication handler must not wait for the CONNACK packet.");
|
||||
}
|
||||
|
||||
throw new MqttProtocolViolationException("Received other packet than AUTH while authenticating.");
|
||||
}
|
||||
|
||||
public Task SendAsync(SendMqttEnhancedAuthenticationDataOptions options, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var authPacket = new MqttAuthPacket
|
||||
{
|
||||
ReasonCode = MqttAuthenticateReasonCode.ContinueAuthentication,
|
||||
AuthenticationMethod = AuthenticationMethod,
|
||||
AuthenticationData = options.Data,
|
||||
UserProperties = options.UserProperties,
|
||||
ReasonString = options.ReasonString
|
||||
};
|
||||
|
||||
return _channelAdapter.SendPacketAsync(authPacket, cancellationToken);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -8,7 +8,7 @@ using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public class MqttExtendedAuthenticationExchangeData
|
||||
public class MqttEnhancedAuthenticationExchangeData
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the authentication data.
|
||||
@@ -0,0 +1,22 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public sealed class ReceiveMqttEnhancedAuthenticationDataResult
|
||||
{
|
||||
public byte[] AuthenticationData { get; init; }
|
||||
|
||||
public string AuthenticationMethod { get; init; }
|
||||
|
||||
public MqttAuthenticateReasonCode ReasonCode { get; init; }
|
||||
|
||||
public string ReasonString { get; init; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet;
|
||||
|
||||
public sealed class SendMqttEnhancedAuthenticationDataOptions
|
||||
{
|
||||
public byte[] Data { get; init; }
|
||||
|
||||
public string ReasonString { get; init; }
|
||||
|
||||
public List<MqttUserProperty> UserProperties { get; init; }
|
||||
}
|
||||
@@ -8,8 +8,12 @@
|
||||
* Namespace changes **(BREAKING CHANGE)**
|
||||
* Removal of Managed Client **(BREAKING CHANGE)**
|
||||
* Client: MQTT 5.0.0 is now the default version when connecting with a server **(BREAKING CHANGE)**
|
||||
* Client: Fixed enhanced authentication.
|
||||
* Client: Exposed WebSocket compression options in MQTT client options (thanks to @victornor, #2127)
|
||||
* Server: Set default for "MaxPendingMessagesPerClient" to 1000 **(BREAKING CHANGE)**
|
||||
* Server: Set SSL version to "None" which will let the OS choose the version **(BREAKING CHANGE)**
|
||||
* Server: Fixed enhanced authentication.
|
||||
* Server: Set default for "MaxPendingMessagesPerClient" to 1000 **(BREAKING CHANGE)**
|
||||
* Server: Set SSL version to "None" which will let the OS choose the version **(BREAKING CHANGE)**
|
||||
* Server: Added API for getting a single session (thanks to @AntonSmolkov, #2131)
|
||||
* Server: Fixed "TryPrivate" (Mosquitto feature) handling (thanks to @victornor, #2125) **(BREAKING CHANGE)**
|
||||
|
||||
Reference in New Issue
Block a user