mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-15 17:00:03 +08:00
Compare commits
24
Commits
v5.0.0-rc1
...
v5.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
948c7c2b69 | ||
|
|
1cdc343e6d | ||
|
|
27771f0146 | ||
|
|
e7de2b913a | ||
|
|
3dd3279656 | ||
|
|
c5934bf18b | ||
|
|
7715bf37dc | ||
|
|
9dbe1e4b12 | ||
|
|
f13b3eb6bb | ||
|
|
8eb884fa5d | ||
|
|
9f81a2bb71 | ||
|
|
e2f8d0f0ac | ||
|
|
981ea02149 | ||
|
|
8566fd5395 | ||
|
|
8089c6bdbc | ||
|
|
ef5b6c9b32 | ||
|
|
4aa4a12e47 | ||
|
|
6bf2b4eaca | ||
|
|
6ef6b57064 | ||
|
|
7d4027958c | ||
|
|
bdb74642c2 | ||
|
|
63d85184e1 | ||
|
|
0506857f0d | ||
|
|
6417baa081 |
@@ -3,8 +3,8 @@ name: CI
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
VERSION: "5.0.0.${{github.run_number}}"
|
||||
PACKAGE_SUFFIX: "-RC"
|
||||
VERSION: "5.0.1.${{github.run_number}}"
|
||||
PACKAGE_SUFFIX: ""
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -107,7 +107,8 @@ jobs:
|
||||
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||
|
||||
publish-nuget:
|
||||
if: ${{ github.event_name == 'published' }}
|
||||
# Tag schema is 'v5.0.0-rc' etc.
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [sign, test]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,10 +102,7 @@ public static class Server_Retained_Messages_Samples
|
||||
|
||||
public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
|
||||
{
|
||||
if (message == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
|
||||
return new MqttRetainedMessageModel
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ namespace MQTTnet.AspNetCore
|
||||
{
|
||||
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(endpoints);
|
||||
|
||||
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
|
||||
{
|
||||
|
||||
@@ -61,21 +61,21 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||
}
|
||||
}
|
||||
|
||||
public string Endpoint
|
||||
public EndPoint RemoteEndPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
// mqtt over tcp
|
||||
if (_connection.RemoteEndPoint != null)
|
||||
{
|
||||
return _connection.RemoteEndPoint.ToString();
|
||||
return _connection.RemoteEndPoint;
|
||||
}
|
||||
|
||||
// mqtt over websocket
|
||||
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||
if (httpFeature?.RemoteIpAddress != null)
|
||||
{
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -13,10 +13,7 @@ public static class MqttSubProtocolSelector
|
||||
{
|
||||
public static string SelectSubProtocol(HttpRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
|
||||
string subProtocol = null;
|
||||
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues))
|
||||
@@ -29,10 +26,7 @@ public static class MqttSubProtocolSelector
|
||||
|
||||
public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
|
||||
{
|
||||
if (requestedSubProtocolValues == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestedSubProtocolValues));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
|
||||
|
||||
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
|
||||
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -26,12 +27,10 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
|
||||
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
|
||||
{
|
||||
if (webSocket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(webSocket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(webSocket);
|
||||
|
||||
var endpoint = $"{httpContext.Connection.RemoteIpAddress}:{httpContext.Connection.RemotePort}";
|
||||
var remoteAddress = httpContext.Connection.RemoteIpAddress;
|
||||
var remoteEndPoint = remoteAddress == null ? null : new IPEndPoint(remoteAddress, httpContext.Connection.RemotePort);
|
||||
|
||||
var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
|
||||
try
|
||||
@@ -42,7 +41,7 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
|
||||
if (clientHandler != null)
|
||||
{
|
||||
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
|
||||
var channel = new MqttWebSocketChannel(webSocket, endpoint, isSecureConnection, clientCertificate);
|
||||
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
|
||||
|
||||
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
|
||||
{
|
||||
|
||||
@@ -22,10 +22,7 @@ public static class ReaderExtensions
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(formatter);
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
|
||||
@@ -16,15 +16,8 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
services.AddSingleton(options);
|
||||
services.AddHostedMqttServer();
|
||||
@@ -34,10 +27,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
var serverOptionsBuilder = new MqttServerOptionsBuilder();
|
||||
|
||||
@@ -61,10 +51,7 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(
|
||||
s =>
|
||||
@@ -89,20 +76,14 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
services.AddSingleton(logger);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
|
||||
{
|
||||
if (serviceCollection is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceCollection));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(serviceCollection);
|
||||
|
||||
serviceCollection.AddMqttConnectionHandler();
|
||||
serviceCollection.AddHostedMqttServer(configure);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12"/>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace MQTTnet.Benchmarks
|
||||
out SequencePosition observed,
|
||||
out int bytesRead)
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
ArgumentNullException.ThrowIfNull(formatter);
|
||||
|
||||
packet = null;
|
||||
consumed = input.Start;
|
||||
|
||||
@@ -15,6 +15,7 @@ using MQTTnet.Formatter.V3;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Benchmarks
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace MQTTnet.Benchmarks
|
||||
_position = _buffer.Offset;
|
||||
}
|
||||
|
||||
public string Endpoint { get; } = string.Empty;
|
||||
public EndPoint RemoteEndPoint { get; set; }
|
||||
|
||||
public bool IsSecureConnection { get; } = false;
|
||||
|
||||
|
||||
@@ -73,15 +73,8 @@ namespace MQTTnet.Benchmarks
|
||||
|
||||
static bool LegacyMethodByStringSplit(string topic, string filter)
|
||||
{
|
||||
if (topic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
|
||||
if (filter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filter));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(topic);
|
||||
ArgumentNullException.ThrowIfNull(filter);
|
||||
|
||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
||||
{
|
||||
|
||||
@@ -10,16 +10,13 @@ namespace MQTTnet.Extensions.Rpc
|
||||
{
|
||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||
{
|
||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||
}
|
||||
|
||||
|
||||
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
|
||||
var responseTopic = requestTopic + "/response";
|
||||
|
||||
|
||||
@@ -20,15 +20,8 @@ namespace MQTTnet.Extensions.Rpc
|
||||
|
||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||
{
|
||||
if (mqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mqttClient));
|
||||
}
|
||||
|
||||
if (rpcClientOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(rpcClientOptions));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(mqttClient);
|
||||
ArgumentNullException.ThrowIfNull(rpcClientOptions);
|
||||
|
||||
return new MqttRpcClient(mqttClient, rpcClientOptions);
|
||||
}
|
||||
|
||||
@@ -65,10 +65,7 @@ namespace MQTTnet.Extensions.Rpc
|
||||
|
||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (methodName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(methodName);
|
||||
|
||||
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
|
||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||
|
||||
@@ -41,10 +41,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
|
||||
/// </exception>
|
||||
public MqttTopicTemplate(string topicTemplate)
|
||||
{
|
||||
if (topicTemplate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicTemplate));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(topicTemplate);
|
||||
|
||||
MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate);
|
||||
|
||||
|
||||
@@ -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!
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
|
||||
{
|
||||
public ClientAcknowledgedPublishPacketEventArgs(string clientId, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
|
||||
public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
|
||||
{
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
UserName = userName;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
|
||||
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
|
||||
@@ -28,6 +29,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -14,11 +16,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
readonly MqttConnectPacket _connectPacket;
|
||||
|
||||
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems)
|
||||
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
|
||||
{
|
||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
ProtocolVersion = protocolVersion;
|
||||
Endpoint = endpoint;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
|
||||
@@ -35,7 +37,10 @@ namespace MQTTnet.Server
|
||||
/// <summary>
|
||||
/// Gets the endpoint of the connected client.
|
||||
/// </summary>
|
||||
public string Endpoint { get; }
|
||||
public EndPoint RemoteEndPoint { get; }
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the protocol version which is used by the connected client.
|
||||
@@ -52,6 +57,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the password of the connected client.
|
||||
/// </summary>
|
||||
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user properties sent by the client.
|
||||
/// <remarks>MQTT 5.0.0+ feature.</remarks>
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
@@ -12,20 +14,21 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class ClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
readonly MqttConnectPacket _connectPacket;
|
||||
readonly MqttDisconnectPacket _disconnectPacket;
|
||||
|
||||
public ClientDisconnectedEventArgs(
|
||||
string clientId,
|
||||
MqttConnectPacket connectPacket,
|
||||
MqttDisconnectPacket disconnectPacket,
|
||||
MqttClientDisconnectType disconnectType,
|
||||
string endpoint,
|
||||
EndPoint remoteEndPoint,
|
||||
IDictionary sessionItems)
|
||||
{
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
DisconnectType = disconnectType;
|
||||
Endpoint = endpoint;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
|
||||
_connectPacket = connectPacket;
|
||||
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
|
||||
_disconnectPacket = disconnectPacket;
|
||||
}
|
||||
@@ -34,11 +37,24 @@ namespace MQTTnet.Server
|
||||
/// 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 { get; }
|
||||
public string ClientId => _connectPacket.ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the password of the client.
|
||||
/// </summary>
|
||||
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
|
||||
|
||||
public MqttClientDisconnectType DisconnectType { get; }
|
||||
|
||||
public string Endpoint { get; }
|
||||
public EndPoint RemoteEndPoint { get; }
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason code sent by the client.
|
||||
|
||||
@@ -10,9 +10,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class ClientSubscribedTopicEventArgs : EventArgs
|
||||
{
|
||||
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems)
|
||||
public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems)
|
||||
{
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
UserName = userName;
|
||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
@@ -23,6 +24,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,9 +9,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
|
||||
{
|
||||
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems)
|
||||
public ClientUnsubscribedTopicEventArgs(string clientId, string userName, string topicFilter, IDictionary sessionItems)
|
||||
{
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
UserName = userName;
|
||||
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
@@ -22,6 +23,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
@@ -11,11 +12,12 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class InterceptingPacketEventArgs : EventArgs
|
||||
{
|
||||
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string endpoint, MqttPacket packet, IDictionary sessionItems)
|
||||
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems)
|
||||
{
|
||||
CancellationToken = cancellationToken;
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
Endpoint = endpoint;
|
||||
UserName = userName;
|
||||
RemoteEndPoint = remoteEndPoint;
|
||||
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
|
||||
SessionItems = sessionItems;
|
||||
}
|
||||
@@ -31,10 +33,18 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the endpoint of the sending or receiving client.
|
||||
/// </summary>
|
||||
public string Endpoint { get; }
|
||||
public EndPoint RemoteEndPoint { get; }
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MQTT packet which was received or will be sent.
|
||||
|
||||
@@ -10,11 +10,12 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class InterceptingPublishEventArgs : EventArgs
|
||||
{
|
||||
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, IDictionary sessionItems)
|
||||
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems)
|
||||
{
|
||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||
CancellationToken = cancellationToken;
|
||||
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
|
||||
UserName = userName;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
|
||||
@@ -31,6 +32,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
public bool CloseConnection { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,12 +15,14 @@ namespace MQTTnet.Server
|
||||
public InterceptingSubscriptionEventArgs(
|
||||
CancellationToken cancellationToken,
|
||||
string clientId,
|
||||
string userName,
|
||||
MqttSessionStatus session,
|
||||
MqttTopicFilter topicFilter,
|
||||
List<MqttUserProperty> userProperties)
|
||||
{
|
||||
CancellationToken = cancellationToken;
|
||||
ClientId = clientId;
|
||||
UserName = userName;
|
||||
Session = session;
|
||||
TopicFilter = topicFilter;
|
||||
UserProperties = userProperties;
|
||||
@@ -37,6 +39,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the broker should close the client connection.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,10 +12,11 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
|
||||
{
|
||||
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties)
|
||||
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties)
|
||||
{
|
||||
CancellationToken = cancellationToken;
|
||||
ClientId = clientId;
|
||||
UserName = userName;
|
||||
SessionItems = sessionItems;
|
||||
Topic = topic;
|
||||
UserProperties = userProperties;
|
||||
@@ -32,6 +33,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string ClientId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the client.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the broker should close the client connection.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,9 +9,10 @@ namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class SessionDeletedEventArgs : EventArgs
|
||||
{
|
||||
public SessionDeletedEventArgs(string id, IDictionary sessionItems)
|
||||
public SessionDeletedEventArgs(string id, string userName, IDictionary sessionItems)
|
||||
{
|
||||
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
UserName = userName;
|
||||
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
|
||||
}
|
||||
|
||||
@@ -20,6 +21,11 @@ namespace MQTTnet.Server
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user name of the session.
|
||||
/// </summary>
|
||||
public string UserName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,189 +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 string Endpoint => ChannelAdapter.Endpoint;
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace MQTTnet.Server.Exceptions;
|
||||
|
||||
public class MqttPendingMessagesOverflowException : Exception
|
||||
{
|
||||
public MqttPendingMessagesOverflowException(string sessionId, MqttPendingMessagesOverflowStrategy overflowStrategy) : base(
|
||||
$"Send buffer max pending messages overflow occurred for session '{sessionId}'. Strategy: {overflowStrategy}.")
|
||||
{
|
||||
SessionId = sessionId;
|
||||
OverflowStrategy = overflowStrategy;
|
||||
}
|
||||
|
||||
public MqttPendingMessagesOverflowStrategy OverflowStrategy { get; }
|
||||
|
||||
public string SessionId { get; }
|
||||
}
|
||||
@@ -23,4 +23,6 @@ public sealed class InjectedMqttApplicationMessage
|
||||
public IDictionary CustomSessionItems { get; set; }
|
||||
|
||||
public string SenderClientId { get; set; } = string.Empty;
|
||||
|
||||
public string SenderUserName { get; set; }
|
||||
}
|
||||
@@ -167,11 +167,11 @@ namespace MQTTnet.Server.Internal.Adapter
|
||||
async Task TryHandleClientConnectionAsync(CrossPlatformSocket clientSocket)
|
||||
{
|
||||
Stream stream = null;
|
||||
string remoteEndPoint = null;
|
||||
EndPoint remoteEndPoint = null;
|
||||
|
||||
try
|
||||
{
|
||||
remoteEndPoint = clientSocket.RemoteEndPoint.ToString();
|
||||
remoteEndPoint = clientSocket.RemoteEndPoint;
|
||||
|
||||
_logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint);
|
||||
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttConnAckPacketFactory
|
||||
{
|
||||
public static MqttConnAckPacket Create(ValidatingConnectionEventArgs validatingConnectionEventArgs)
|
||||
{
|
||||
if (validatingConnectionEventArgs == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(validatingConnectionEventArgs));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(validatingConnectionEventArgs);
|
||||
|
||||
var connAckPacket = new MqttConnAckPacket
|
||||
{
|
||||
|
||||
@@ -11,15 +11,8 @@ public static class MqttPubAckPacketFactory
|
||||
{
|
||||
public static MqttPubAckPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||
{
|
||||
if (publishPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(publishPacket));
|
||||
}
|
||||
|
||||
if (dispatchApplicationMessageResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatchApplicationMessageResult));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(publishPacket);
|
||||
ArgumentNullException.ThrowIfNull(dispatchApplicationMessageResult);
|
||||
|
||||
var pubAckPacket = new MqttPubAckPacket
|
||||
{
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttPubCompPacketFactory
|
||||
{
|
||||
public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode)
|
||||
{
|
||||
if (pubRelPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pubRelPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(pubRelPacket);
|
||||
|
||||
return new MqttPubCompPacket
|
||||
{
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttPubRecPacketFactory
|
||||
{
|
||||
public static MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||
{
|
||||
if (publishPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(publishPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(publishPacket);
|
||||
|
||||
var pubRecPacket = new MqttPubRecPacket
|
||||
{
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttPubRelPacketFactory
|
||||
{
|
||||
public static MqttPubRelPacket Create(MqttPubRecPacket pubRecPacket, MqttApplicationMessageReceivedReasonCode reasonCode)
|
||||
{
|
||||
if (pubRecPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pubRecPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(pubRecPacket);
|
||||
|
||||
return new MqttPubRelPacket
|
||||
{
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttPublishPacketFactory
|
||||
{
|
||||
public static MqttPublishPacket Create(MqttConnectPacket connectPacket)
|
||||
{
|
||||
if (connectPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connectPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(connectPacket);
|
||||
|
||||
if (!connectPacket.WillFlag)
|
||||
{
|
||||
@@ -46,10 +43,7 @@ public static class MqttPublishPacketFactory
|
||||
|
||||
public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessage);
|
||||
|
||||
// Copy all values to their matching counterparts.
|
||||
// The not supported values in MQTT 3.1.1 are not serialized (excluded) later.
|
||||
@@ -75,10 +69,7 @@ public static class MqttPublishPacketFactory
|
||||
|
||||
public static MqttPublishPacket Create(MqttRetainedMessageMatch retainedMessage)
|
||||
{
|
||||
if (retainedMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(retainedMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(retainedMessage);
|
||||
|
||||
var publishPacket = Create(retainedMessage.ApplicationMessage);
|
||||
publishPacket.QualityOfServiceLevel = retainedMessage.SubscriptionQualityOfServiceLevel;
|
||||
|
||||
@@ -10,15 +10,8 @@ public static class MqttSubAckPacketFactory
|
||||
{
|
||||
public static MqttSubAckPacket Create(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult)
|
||||
{
|
||||
if (subscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscribePacket));
|
||||
}
|
||||
|
||||
if (subscribeResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscribeResult));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(subscribePacket);
|
||||
ArgumentNullException.ThrowIfNull(subscribeResult);
|
||||
|
||||
var subAckPacket = new MqttSubAckPacket
|
||||
{
|
||||
|
||||
@@ -10,15 +10,8 @@ namespace MQTTnet.Server.Internal.Formatter
|
||||
{
|
||||
public static MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, UnsubscribeResult unsubscribeResult)
|
||||
{
|
||||
if (unsubscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
}
|
||||
|
||||
if (unsubscribeResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(unsubscribeResult));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(unsubscribePacket);
|
||||
ArgumentNullException.ThrowIfNull(unsubscribeResult);
|
||||
|
||||
var unsubAckPacket = new MqttUnsubAckPacket
|
||||
{
|
||||
|
||||
@@ -19,29 +19,22 @@ namespace MQTTnet.Server.Internal;
|
||||
public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification, IDisposable
|
||||
{
|
||||
readonly Dictionary<string, MqttConnectedClient> _clients = new(4096);
|
||||
|
||||
readonly AsyncLock _createConnectionSyncRoot = new();
|
||||
|
||||
readonly MqttServerEventContainer _eventContainer;
|
||||
readonly MqttNetSourceLogger _logger;
|
||||
readonly MqttServerOptions _options;
|
||||
|
||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
readonly IMqttNetLogger _rootLogger;
|
||||
|
||||
readonly ReaderWriterLockSlim _sessionsManagementLock = new();
|
||||
|
||||
// The _sessions dictionary contains all session, the _subscriberSessions hash set contains subscriber sessions only.
|
||||
// See the MqttSubscription object for a detailed explanation.
|
||||
readonly MqttSessionsStorage _sessionsStorage = new();
|
||||
readonly HashSet<MqttSession> _subscriberSessions = new();
|
||||
readonly HashSet<MqttSession> _subscriberSessions = [];
|
||||
|
||||
public MqttClientSessionsManager(MqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, MqttServerEventContainer eventContainer, IMqttNetLogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttClientSessionsManager));
|
||||
_rootLogger = logger;
|
||||
@@ -53,10 +46,7 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
public async Task CloseAllConnections(MqttServerClientDisconnectOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
List<MqttConnectedClient> connections;
|
||||
lock (_clients)
|
||||
@@ -111,7 +101,7 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
{
|
||||
if (_eventContainer.SessionDeletedEvent.HasHandlers && session != null)
|
||||
{
|
||||
var eventArgs = new SessionDeletedEventArgs(clientId, session.Items);
|
||||
var eventArgs = new SessionDeletedEventArgs(clientId, session.UserName, session.Items);
|
||||
await _eventContainer.SessionDeletedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -127,6 +117,7 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
||||
string senderId,
|
||||
string senderUserName,
|
||||
IDictionary senderSessionItems,
|
||||
MqttApplicationMessage applicationMessage,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -140,7 +131,7 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
// Allow the user to intercept application message...
|
||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||
{
|
||||
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems);
|
||||
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderUserName, senderSessionItems);
|
||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||
{
|
||||
// This can happen if a topic alias us used but the topic is
|
||||
@@ -320,6 +311,25 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
return Task.FromResult((IList<MqttClientStatus>)result);
|
||||
}
|
||||
|
||||
public Task<MqttSessionStatus> GetSessionStatus(string id)
|
||||
{
|
||||
_sessionsManagementLock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (!_sessionsStorage.TryGetSession(id, out var session))
|
||||
{
|
||||
throw new InvalidOperationException($"Session with ID '{id}' not found.");
|
||||
}
|
||||
|
||||
var sessionStatus = new MqttSessionStatus(session);
|
||||
return Task.FromResult(sessionStatus);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsManagementLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IList<MqttSessionStatus>> GetSessionsStatus()
|
||||
{
|
||||
var result = new List<MqttSessionStatus>();
|
||||
@@ -354,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)
|
||||
@@ -371,7 +381,11 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
if (_eventContainer.ClientConnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ClientConnectedEventArgs(connectPacket, channelAdapter.PacketFormatterAdapter.ProtocolVersion, channelAdapter.Endpoint, connectedClient.Session.Items);
|
||||
var eventArgs = new ClientConnectedEventArgs(
|
||||
connectPacket,
|
||||
channelAdapter.PacketFormatterAdapter.ProtocolVersion,
|
||||
channelAdapter.RemoteEndPoint,
|
||||
connectedClient.Session.Items);
|
||||
|
||||
await _eventContainer.ClientConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
@@ -409,12 +423,17 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
}
|
||||
}
|
||||
|
||||
var endpoint = connectedClient.Endpoint;
|
||||
var endpoint = connectedClient.RemoteEndPoint;
|
||||
|
||||
if (connectedClient.Id != null && !connectedClient.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||
{
|
||||
var disconnectType = connectedClient.DisconnectPacket != null ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean;
|
||||
var eventArgs = new ClientDisconnectedEventArgs(connectedClient.Id, connectedClient.DisconnectPacket, disconnectType, endpoint, connectedClient.Session.Items);
|
||||
var eventArgs = new ClientDisconnectedEventArgs(
|
||||
connectedClient.ConnectPacket,
|
||||
connectedClient.DisconnectPacket,
|
||||
disconnectType,
|
||||
endpoint,
|
||||
connectedClient.Session.Items);
|
||||
|
||||
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
@@ -481,15 +500,8 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
public async Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
|
||||
{
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topicFilters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topicFilters);
|
||||
|
||||
var fakeSubscribePacket = new MqttSubscribePacket();
|
||||
fakeSubscribePacket.TopicFilters.AddRange(topicFilters);
|
||||
@@ -510,15 +522,8 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
public Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters)
|
||||
{
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topicFilters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topicFilters);
|
||||
|
||||
var fakeUnsubscribePacket = new MqttUnsubscribePacket();
|
||||
fakeUnsubscribePacket.TopicFilters.AddRange(topicFilters);
|
||||
@@ -611,7 +616,12 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
|
||||
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ClientDisconnectedEventArgs(oldConnectedClient.Id, null, MqttClientDisconnectType.Takeover, oldConnectedClient.Endpoint, oldConnectedClient.Session.Items);
|
||||
var eventArgs = new ClientDisconnectedEventArgs(
|
||||
oldConnectedClient.ConnectPacket,
|
||||
null,
|
||||
MqttClientDisconnectType.Takeover,
|
||||
oldConnectedClient.RemoteEndPoint,
|
||||
oldConnectedClient.Session.Items);
|
||||
|
||||
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||
}
|
||||
@@ -675,14 +685,14 @@ public sealed class MqttClientSessionsManager : ISubscriptionChangedNotification
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
|
||||
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.RemoteEndPoint);
|
||||
}
|
||||
catch (MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
|
||||
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.RemoteEndPoint);
|
||||
}
|
||||
|
||||
_logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint);
|
||||
_logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.RemoteEndPoint);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -730,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.
|
||||
|
||||
@@ -50,10 +50,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public void HandleReceivedPacket(MqttPacket packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(packet));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(packet);
|
||||
|
||||
// This class is tracking all values from Clients perspective!
|
||||
LastPacketSentTimestamp = DateTime.UtcNow;
|
||||
@@ -74,10 +71,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public void HandleSentPacket(MqttPacket packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(packet));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(packet);
|
||||
|
||||
// This class is tracking all values from Clients perspective!
|
||||
LastPacketReceivedTimestamp = DateTime.UtcNow;
|
||||
|
||||
@@ -161,10 +161,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
if (subscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subscribePacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(subscribePacket);
|
||||
|
||||
var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false);
|
||||
var result = new SubscribeResult(subscribePacket.TopicFilters.Count);
|
||||
@@ -212,7 +209,7 @@ namespace MQTTnet.Server.Internal
|
||||
{
|
||||
foreach (var finalTopicFilter in finalTopicFilters)
|
||||
{
|
||||
var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, finalTopicFilter, _session.Items);
|
||||
var eventArgs = new ClientSubscribedTopicEventArgs(_session.Id, _session.UserName, finalTopicFilter, _session.Items);
|
||||
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -222,10 +219,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public async Task<UnsubscribeResult> Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
if (unsubscribePacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(unsubscribePacket);
|
||||
|
||||
var result = new UnsubscribeResult();
|
||||
|
||||
@@ -303,7 +297,7 @@ namespace MQTTnet.Server.Internal
|
||||
{
|
||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||
{
|
||||
var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, topicFilter, _session.Items);
|
||||
var eventArgs = new ClientUnsubscribedTopicEventArgs(_session.Id, _session.UserName, topicFilter, _session.Items);
|
||||
await _eventContainer.ClientUnsubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -440,7 +434,7 @@ namespace MQTTnet.Server.Internal
|
||||
var retainedMessageMatch = new MqttRetainedMessageMatch(retainedMessage, createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel);
|
||||
if (retainedMessageMatch.SubscriptionQualityOfServiceLevel > retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel)
|
||||
{
|
||||
// UPGRADING the QoS is not allowed!
|
||||
// UPGRADING the QoS is not allowed!
|
||||
// From MQTT spec: Subscribing to a Topic Filter at QoS 2 is equivalent to saying
|
||||
// "I would like to receive Messages matching this filter at the QoS with which they were published".
|
||||
// This means a publisher is responsible for determining the maximum QoS a Message can be delivered at,
|
||||
@@ -455,7 +449,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
subscribeResult.RetainedMessages.Add(retainedMessageMatch);
|
||||
|
||||
// Clear the retained message from the list because the client should receive every message only
|
||||
// Clear the retained message from the list because the client should receive every message only
|
||||
// one time even if multiple subscriptions affect them.
|
||||
retainedMessages[index] = null;
|
||||
}
|
||||
@@ -466,7 +460,7 @@ namespace MQTTnet.Server.Internal
|
||||
List<MqttUserProperty> userProperties,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, new MqttSessionStatus(_session), topicFilter, userProperties);
|
||||
var eventArgs = new InterceptingSubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, new MqttSessionStatus(_session), topicFilter, userProperties);
|
||||
|
||||
if (topicFilter.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
@@ -499,7 +493,7 @@ namespace MQTTnet.Server.Internal
|
||||
List<MqttUserProperty> userProperties,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, _session.Id, _session.Items, topicFilter, userProperties)
|
||||
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, _session.Id, _session.UserName, _session.Items, topicFilter, userProperties)
|
||||
{
|
||||
Response =
|
||||
{
|
||||
|
||||
@@ -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.Net;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics.Logger;
|
||||
using MQTTnet.Exceptions;
|
||||
@@ -45,13 +46,10 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||
|
||||
ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||
Endpoint = channelAdapter.Endpoint;
|
||||
RemoteEndPoint = channelAdapter.RemoteEndPoint;
|
||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttConnectedClient));
|
||||
}
|
||||
@@ -62,18 +60,20 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
|
||||
public MqttDisconnectPacket DisconnectPacket { get; private set; }
|
||||
|
||||
public string Endpoint { get; }
|
||||
|
||||
public string Id => ConnectPacket.ClientId;
|
||||
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
public bool IsTakenOver { get; set; }
|
||||
|
||||
public EndPoint RemoteEndPoint { get; }
|
||||
|
||||
public MqttSession Session { get; }
|
||||
|
||||
public MqttClientStatistics Statistics { get; } = new();
|
||||
|
||||
public string UserName => ConnectPacket.Username;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationToken?.Dispose();
|
||||
@@ -119,7 +119,7 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
var willPublishPacket = MqttPublishPacketFactory.Create(Session.LatestConnectPacket);
|
||||
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
|
||||
|
||||
_ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None);
|
||||
_ = _sessionsManager.DispatchApplicationMessage(Id, UserName, Session.Items, willApplicationMessage, CancellationToken.None);
|
||||
Session.WillMessageSent = true;
|
||||
|
||||
_logger.Info("Client '{0}': Published will message", Id);
|
||||
@@ -176,7 +176,7 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
{
|
||||
if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new ClientAcknowledgedPublishPacketEventArgs(Id, Session.Items, publishPacket, acknowledgePacket);
|
||||
var eventArgs = new ClientAcknowledgedPublishPacketEventArgs(Id, UserName, Session.Items, publishPacket, acknowledgePacket);
|
||||
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
|
||||
}
|
||||
|
||||
@@ -219,7 +219,8 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
|
||||
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||
|
||||
var dispatchApplicationMessageResult = await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
var dispatchApplicationMessageResult =
|
||||
await _sessionsManager.DispatchApplicationMessage(Id, UserName, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (dispatchApplicationMessageResult.CloseConnection)
|
||||
{
|
||||
@@ -341,7 +342,7 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
return packet;
|
||||
}
|
||||
|
||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, UserName, RemoteEndPoint, packet, Session.Items);
|
||||
await _eventContainer.InterceptingOutboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||
|
||||
if (!interceptingPacketEventArgs.ProcessPacket || packet == null)
|
||||
@@ -387,7 +388,7 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
|
||||
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
||||
{
|
||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, currentPacket, Session.Items);
|
||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, UserName, RemoteEndPoint, currentPacket, Session.Items);
|
||||
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||
currentPacket = interceptingPacketEventArgs.Packet;
|
||||
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
||||
@@ -563,10 +564,8 @@ public sealed class MqttConnectedClient : IDisposable
|
||||
|
||||
var disconnectPacket = MqttDisconnectPacketFactory.Create(options);
|
||||
|
||||
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||
{
|
||||
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
using var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout);
|
||||
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,8 +20,7 @@ namespace MQTTnet.Server.Internal
|
||||
{
|
||||
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
||||
|
||||
if (logger == null)
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttRetainedMessagesManager));
|
||||
}
|
||||
@@ -53,10 +53,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public async Task UpdateMessage(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessage);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -27,10 +27,7 @@ namespace MQTTnet.Server.Internal
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor));
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server.Exceptions;
|
||||
|
||||
namespace MQTTnet.Server.Internal;
|
||||
|
||||
@@ -53,6 +54,8 @@ public sealed class MqttSession : IDisposable
|
||||
|
||||
public string Id => _connectPacket.ClientId;
|
||||
|
||||
public string UserName => _connectPacket.Username;
|
||||
|
||||
public IDictionary Items { get; }
|
||||
|
||||
public MqttConnectPacket LatestConnectPacket { get; set; }
|
||||
@@ -109,10 +112,11 @@ public sealed class MqttSession : IDisposable
|
||||
|
||||
public EnqueueDataPacketResult EnqueueDataPacket(MqttPacketBusItem packetBusItem)
|
||||
{
|
||||
if (_packetBus.ItemsCount(MqttPacketBusPartition.Data) >= _serverOptions.MaxPendingMessagesPerClient)
|
||||
if (PendingDataPacketsCount >= _serverOptions.MaxPendingMessagesPerClient)
|
||||
{
|
||||
if (_serverOptions.PendingMessagesOverflowStrategy == MqttPendingMessagesOverflowStrategy.DropNewMessage)
|
||||
{
|
||||
packetBusItem.Fail(new MqttPendingMessagesOverflowException(Id, _serverOptions.PendingMessagesOverflowStrategy));
|
||||
return EnqueueDataPacketResult.Dropped;
|
||||
}
|
||||
|
||||
@@ -121,10 +125,15 @@ public sealed class MqttSession : IDisposable
|
||||
// Only drop from the data partition. Dropping from control partition might break the connection
|
||||
// because the client does not receive PINGREQ packets etc. any longer.
|
||||
var firstItem = _packetBus.DropFirstItem(MqttPacketBusPartition.Data);
|
||||
if (firstItem != null && _eventContainer.QueuedApplicationMessageOverwrittenEvent.HasHandlers)
|
||||
if (firstItem != null)
|
||||
{
|
||||
var eventArgs = new QueueMessageOverwrittenEventArgs(Id, firstItem.Packet);
|
||||
_eventContainer.QueuedApplicationMessageOverwrittenEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
firstItem.Fail(new MqttPendingMessagesOverflowException(Id, _serverOptions.PendingMessagesOverflowStrategy));
|
||||
|
||||
if (_eventContainer.QueuedApplicationMessageOverwrittenEvent.HasHandlers)
|
||||
{
|
||||
var eventArgs = new QueueMessageOverwrittenEventArgs(Id, firstItem.Packet);
|
||||
_eventContainer.QueuedApplicationMessageOverwrittenEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public bool TryGetSession(string id, out MqttSession session)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
|
||||
if (!_sessions.TryGetValue(id, out session))
|
||||
{
|
||||
@@ -63,10 +60,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public bool TryRemoveSession(string id, out MqttSession session)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
|
||||
if (_sessions.TryGetValue(id, out session))
|
||||
{
|
||||
@@ -79,10 +73,7 @@ namespace MQTTnet.Server.Internal
|
||||
|
||||
public void UpdateSession(string id, MqttSession session)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
|
||||
_sessions[id] = session;
|
||||
}
|
||||
|
||||
@@ -31,10 +31,7 @@ public class MqttServer : Disposable
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (adapters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(adapters);
|
||||
|
||||
_adapters = adapters.ToList();
|
||||
|
||||
@@ -202,15 +199,8 @@ public class MqttServer : Disposable
|
||||
|
||||
public Task DisconnectClientAsync(string id, MqttServerClientDisconnectOptions options)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(id);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
ThrowIfNotStarted();
|
||||
|
||||
@@ -226,10 +216,7 @@ public class MqttServer : Disposable
|
||||
|
||||
public Task<MqttApplicationMessage> GetRetainedMessageAsync(string topic)
|
||||
{
|
||||
if (topic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(topic);
|
||||
|
||||
ThrowIfNotStarted();
|
||||
|
||||
@@ -250,17 +237,17 @@ public class MqttServer : Disposable
|
||||
return _clientSessionsManager.GetSessionsStatus();
|
||||
}
|
||||
|
||||
public Task<MqttSessionStatus> GetSessionAsync(string id)
|
||||
{
|
||||
ThrowIfNotStarted();
|
||||
|
||||
return _clientSessionsManager.GetSessionStatus(id);
|
||||
}
|
||||
|
||||
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (injectedApplicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(injectedApplicationMessage));
|
||||
}
|
||||
|
||||
if (injectedApplicationMessage.ApplicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(injectedApplicationMessage.ApplicationMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(injectedApplicationMessage);
|
||||
ArgumentNullException.ThrowIfNull(injectedApplicationMessage.ApplicationMessage);
|
||||
|
||||
MqttTopicValidator.ThrowIfInvalid(injectedApplicationMessage.ApplicationMessage.Topic);
|
||||
|
||||
@@ -275,6 +262,7 @@ public class MqttServer : Disposable
|
||||
|
||||
return _clientSessionsManager.DispatchApplicationMessage(
|
||||
injectedApplicationMessage.SenderClientId,
|
||||
injectedApplicationMessage.SenderUserName,
|
||||
sessionItems,
|
||||
injectedApplicationMessage.ApplicationMessage,
|
||||
cancellationToken);
|
||||
@@ -306,10 +294,7 @@ public class MqttServer : Disposable
|
||||
|
||||
public async Task StopAsync(MqttServerStopOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -343,15 +328,8 @@ public class MqttServer : Disposable
|
||||
|
||||
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
|
||||
{
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topicFilters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topicFilters);
|
||||
|
||||
foreach (var topicFilter in topicFilters)
|
||||
{
|
||||
@@ -366,15 +344,8 @@ public class MqttServer : Disposable
|
||||
|
||||
public Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters)
|
||||
{
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topicFilters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topicFilters);
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
@@ -384,10 +355,7 @@ public class MqttServer : Disposable
|
||||
|
||||
public Task UpdateRetainedMessageAsync(MqttApplicationMessage retainedMessage)
|
||||
{
|
||||
if (retainedMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(retainedMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(retainedMessage);
|
||||
|
||||
ThrowIfDisposed();
|
||||
ThrowIfNotStarted();
|
||||
|
||||
@@ -13,10 +13,7 @@ public static class MqttServerExtensions
|
||||
{
|
||||
public static Task DisconnectClientAsync(this MqttServer server, string id, MqttDisconnectReasonCode reasonCode = MqttDisconnectReasonCode.NormalDisconnection)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
|
||||
return server.DisconnectClientAsync(id, new MqttServerClientDisconnectOptions { ReasonCode = reasonCode });
|
||||
}
|
||||
@@ -28,15 +25,8 @@ public static class MqttServerExtensions
|
||||
MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce,
|
||||
bool retain = false)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
if (topic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
ArgumentNullException.ThrowIfNull(topic);
|
||||
|
||||
var payloadBuffer = EmptyBuffer.Array;
|
||||
if (payload is string stringPayload)
|
||||
@@ -57,50 +47,25 @@ public static class MqttServerExtensions
|
||||
|
||||
public static Task StopAsync(this MqttServer server)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
|
||||
return server.StopAsync(new MqttServerStopOptions());
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this MqttServer server, string clientId, params MqttTopicFilter[] topicFilters)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topicFilters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topicFilters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topicFilters);
|
||||
|
||||
return server.SubscribeAsync(clientId, topicFilters);
|
||||
}
|
||||
|
||||
public static Task SubscribeAsync(this MqttServer server, string clientId, string topic)
|
||||
{
|
||||
if (server == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientId));
|
||||
}
|
||||
|
||||
if (topic == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(topic));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(server);
|
||||
ArgumentNullException.ThrowIfNull(clientId);
|
||||
ArgumentNullException.ThrowIfNull(topic);
|
||||
|
||||
var topicFilters = new MqttTopicFilterBuilder().WithTopic(topic).Build();
|
||||
return server.SubscribeAsync(clientId, topicFilters);
|
||||
|
||||
@@ -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);
|
||||
@@ -39,10 +45,7 @@ public sealed class MqttServerFactory
|
||||
|
||||
public MqttServer CreateMqttServer(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
var serverAdapters = DefaultServerAdapters.Select(a => a.Invoke(this));
|
||||
return CreateMqttServer(options, serverAdapters, logger);
|
||||
@@ -50,25 +53,15 @@ public sealed class MqttServerFactory
|
||||
|
||||
public MqttServer CreateMqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> serverAdapters, IMqttNetLogger logger)
|
||||
{
|
||||
if (serverAdapters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serverAdapters));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(serverAdapters);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
return new MqttServer(options, serverAdapters, logger);
|
||||
}
|
||||
|
||||
public MqttServer CreateMqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> serverAdapters)
|
||||
{
|
||||
if (serverAdapters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serverAdapters));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(serverAdapters);
|
||||
|
||||
return new MqttServer(options, serverAdapters, DefaultLogger);
|
||||
}
|
||||
|
||||
@@ -2,38 +2,35 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
namespace MQTTnet.Server;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
public sealed class MqttServerOptions
|
||||
{
|
||||
public sealed class MqttServerOptions
|
||||
{
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(100);
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(100);
|
||||
|
||||
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions();
|
||||
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new();
|
||||
|
||||
public bool EnablePersistentSessions { get; set; }
|
||||
public bool EnablePersistentSessions { get; set; }
|
||||
|
||||
public MqttServerKeepAliveOptions KeepAliveOptions { get; } = new MqttServerKeepAliveOptions();
|
||||
public MqttServerKeepAliveOptions KeepAliveOptions { get; } = new();
|
||||
|
||||
public int MaxPendingMessagesPerClient { get; set; } = 250;
|
||||
public int MaxPendingMessagesPerClient { get; set; } = 1000;
|
||||
|
||||
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage;
|
||||
public MqttPendingMessagesOverflowStrategy PendingMessagesOverflowStrategy { get; set; } = MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage;
|
||||
|
||||
public MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsTcpEndpointOptions();
|
||||
public MqttServerTlsTcpEndpointOptions TlsEndpointOptions { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default and initial size of the packet write buffer.
|
||||
/// It is recommended to set this to a value close to the usual expected packet size * 1.5.
|
||||
/// Do not change this value when no memory issues are experienced.
|
||||
/// </summary>
|
||||
public int WriterBufferSize { get; set; } = 4096;
|
||||
/// <summary>
|
||||
/// Gets or sets the default and initial size of the packet write buffer.
|
||||
/// It is recommended to set this to a value close to the usual expected packet size * 1.5.
|
||||
/// Do not change this value when no memory issues are experienced.
|
||||
/// </summary>
|
||||
public int WriterBufferSize { get; set; } = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size of the buffer writer. The writer will reduce its internal buffer
|
||||
/// to this value after serializing a packet.
|
||||
/// Do not change this value when no memory issues are experienced.
|
||||
/// </summary>
|
||||
public int WriterBufferSizeMax { get; set; } = 65535;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum size of the buffer writer. The writer will reduce its internal buffer
|
||||
/// to this value after serializing a packet.
|
||||
/// Do not change this value when no memory issues are experienced.
|
||||
/// </summary>
|
||||
public int WriterBufferSizeMax { get; set; } = 65535;
|
||||
}
|
||||
@@ -115,6 +115,12 @@ namespace MQTTnet.Server
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy value)
|
||||
{
|
||||
_options.PendingMessagesOverflowStrategy = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithoutDefaultEndpoint()
|
||||
{
|
||||
_options.DefaultEndpointOptions.IsEnabled = false;
|
||||
@@ -180,10 +186,7 @@ namespace MQTTnet.Server
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value, IMqttServerCertificateCredentials credentials = null)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = new BlobCertificateProvider(value)
|
||||
{
|
||||
@@ -195,10 +198,7 @@ namespace MQTTnet.Server
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
if (certificate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(certificate));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(certificate);
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = new X509CertificateProvider(certificate);
|
||||
return this;
|
||||
@@ -206,10 +206,7 @@ namespace MQTTnet.Server
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(ICertificateProvider certificateProvider)
|
||||
{
|
||||
if (certificateProvider == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(certificateProvider));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(certificateProvider);
|
||||
|
||||
_options.TlsEndpointOptions.CertificateProvider = certificateProvider;
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ namespace MQTTnet.Server
|
||||
|
||||
public bool CheckCertificateRevocation { get; set; }
|
||||
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12;
|
||||
/// <summary>
|
||||
/// The default value is SslProtocols.None, which allows the operating system to choose the best protocol to use, and to block protocols that are not secure.
|
||||
/// </summary>
|
||||
/// <seealso href="https://learn.microsoft.com/en-us/dotnet/api/system.security.authentication.sslprotocols">SslProtocols</seealso>
|
||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.None;
|
||||
|
||||
public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get; set; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using MQTTnet.Formatter;
|
||||
using MQTTnet.Server.Internal;
|
||||
using System.Net;
|
||||
|
||||
namespace MQTTnet.Server;
|
||||
|
||||
@@ -22,7 +23,10 @@ public sealed class MqttClientStatus
|
||||
|
||||
public DateTime ConnectedTimestamp => _client.Statistics.ConnectedTimestamp;
|
||||
|
||||
public string Endpoint => _client.Endpoint;
|
||||
public EndPoint RemoteEndPoint => _client.RemoteEndPoint;
|
||||
|
||||
[Obsolete("Use RemoteEndPoint instead.")]
|
||||
public string Endpoint => RemoteEndPoint?.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client identifier.
|
||||
@@ -50,10 +54,7 @@ public sealed class MqttClientStatus
|
||||
|
||||
public Task DisconnectAsync(MqttServerClientDisconnectOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
return _client.StopAsync(options);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@ public static class MqttClientStatusExtensions
|
||||
|
||||
public static Task DisconnectAsync(this MqttClientStatus clientStatus)
|
||||
{
|
||||
if (clientStatus == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientStatus));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientStatus);
|
||||
|
||||
return clientStatus.DisconnectAsync(DefaultDisconnectOptions);
|
||||
}
|
||||
|
||||
@@ -40,28 +40,65 @@ public sealed class MqttSessionStatus
|
||||
return _session.DeleteAsync();
|
||||
}
|
||||
|
||||
public Task DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
/// <summary>
|
||||
/// Delivers an application message immediately to the session.
|
||||
/// </summary>
|
||||
/// <param name="applicationMessage">The application message to deliver.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation.
|
||||
/// The result contains the <see cref="InjectMqttApplicationMessageResult"/> that includes the packet identifier of the enqueued message.
|
||||
/// </returns>
|
||||
public async Task<InjectMqttApplicationMessageResult> DeliverApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessage);
|
||||
|
||||
var packetBusItem = new MqttPacketBusItem(MqttPublishPacketFactory.Create(applicationMessage));
|
||||
var publishPacket = MqttPublishPacketFactory.Create(applicationMessage);
|
||||
var packetBusItem = new MqttPacketBusItem(publishPacket);
|
||||
_session.EnqueueDataPacket(packetBusItem);
|
||||
|
||||
return packetBusItem.WaitAsync();
|
||||
await packetBusItem.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
var injectResult = new InjectMqttApplicationMessageResult()
|
||||
{
|
||||
PacketIdentifier = publishPacket.PacketIdentifier
|
||||
};
|
||||
|
||||
return injectResult;
|
||||
}
|
||||
|
||||
public Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
/// <summary>
|
||||
/// Attempts to enqueue an application message to the session's send buffer.
|
||||
/// </summary>
|
||||
/// <param name="applicationMessage">The application message to enqueue.</param>
|
||||
/// <param name="injectResult"><see cref="InjectMqttApplicationMessageResult"/> that includes the packet identifier of the enqueued message.</param>
|
||||
/// <returns><c>true</c> if the message was successfully enqueued; otherwise, <c>false</c>.</returns>
|
||||
/// <remarks>
|
||||
/// When <see cref="MqttServerOptions.PendingMessagesOverflowStrategy"/> is set to <see cref="MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage"/>,
|
||||
/// this method always returns <c>true</c>.
|
||||
/// However, an existing message in the queue may be <b>dropped later</b> to make room for the newly enqueued message.
|
||||
/// Such dropped messages can be tracked by subscribing to <see cref="MqttServer.QueuedApplicationMessageOverwrittenAsync"/> event.
|
||||
/// </remarks>
|
||||
public bool TryEnqueueApplicationMessage(MqttApplicationMessage applicationMessage, out InjectMqttApplicationMessageResult injectResult)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
ArgumentNullException.ThrowIfNull(applicationMessage);
|
||||
|
||||
var publishPacket = MqttPublishPacketFactory.Create(applicationMessage);
|
||||
var enqueueDataPacketResult = _session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
||||
|
||||
if (enqueueDataPacketResult != EnqueueDataPacketResult.Enqueued)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
injectResult = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
_session.EnqueueDataPacket(new MqttPacketBusItem(MqttPublishPacketFactory.Create(applicationMessage)));
|
||||
injectResult = new InjectMqttApplicationMessageResult() { PacketIdentifier = publishPacket.PacketIdentifier };
|
||||
return true;
|
||||
}
|
||||
|
||||
[Obsolete("This method is obsolete. Use TryEnqueueApplicationMessage instead.")]
|
||||
public Task EnqueueApplicationMessageAsync(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
TryEnqueueApplicationMessage(applicationMessage, out _);
|
||||
return CompletedTask.Instance;
|
||||
}
|
||||
}
|
||||
@@ -15,21 +15,18 @@ namespace MQTTnet.Server
|
||||
_options.DefaultClientDisconnectOptions = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttServerStopOptionsBuilder WithDefaultClientDisconnectOptions(Action<MqttServerClientDisconnectOptionsBuilder> builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
var optionsBuilder = new MqttServerClientDisconnectOptionsBuilder();
|
||||
builder.Invoke(optionsBuilder);
|
||||
|
||||
|
||||
_options.DefaultClientDisconnectOptions = optionsBuilder.Build();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public MqttServerStopOptions Build()
|
||||
{
|
||||
return _options;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace MQTTnet.TestApp
|
||||
|
||||
public static void ForwardToConsole(MqttNetEventLogger logger)
|
||||
{
|
||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
logger.LogMessagePublished -= PrintToConsole;
|
||||
logger.LogMessagePublished += PrintToConsole;
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace MQTTnet.TestApp
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new MqttServerOptions();
|
||||
var options = new MqttServerOptionsBuilder()
|
||||
.WithDefaultEndpoint()
|
||||
.Build();
|
||||
|
||||
// Extend the timestamp for all messages from clients.
|
||||
// Protect several topics from being subscribed from every client.
|
||||
|
||||
@@ -13,10 +13,11 @@ namespace MQTTnet.Tests
|
||||
public abstract class BaseTestClass
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
protected TestEnvironment CreateTestEnvironment(MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
|
||||
protected TestEnvironment CreateTestEnvironment(
|
||||
MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true)
|
||||
{
|
||||
return new TestEnvironment(TestContext, protocolVersion);
|
||||
return new TestEnvironment(TestContext, protocolVersion, trackUnobservedTaskException);
|
||||
}
|
||||
|
||||
protected Task LongTestDelay()
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests.Clients.MqttClient
|
||||
[TestClass]
|
||||
public sealed class MqttClient_Tests : BaseTestClass
|
||||
{
|
||||
[DataTestMethod]
|
||||
[TestMethod]
|
||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtMostOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||
|
||||
@@ -11,10 +11,7 @@ namespace MQTTnet.Tests.Helpers
|
||||
{
|
||||
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(client);
|
||||
|
||||
return new TestApplicationMessageReceivedHandler(client);
|
||||
}
|
||||
|
||||
@@ -11,17 +11,14 @@ namespace MQTTnet.Tests.Helpers
|
||||
{
|
||||
public static object GetFieldValue(this object source, string fieldName)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field == null)
|
||||
{
|
||||
throw new ArgumentException($"Field {fieldName} not found.");
|
||||
}
|
||||
|
||||
|
||||
return field.GetValue(source);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<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>
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MQTTnet.Channel;
|
||||
using MQTTnet.Internal;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -26,7 +28,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
_stream = new MemoryStream(buffer);
|
||||
}
|
||||
|
||||
public string Endpoint { get; } = "<Test channel>";
|
||||
public EndPoint RemoteEndPoint { get; set; }
|
||||
|
||||
public bool IsSecureConnection { get; } = false;
|
||||
|
||||
|
||||
@@ -18,10 +18,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
|
||||
{
|
||||
if (mqttClient == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(mqttClient));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(mqttClient);
|
||||
|
||||
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
|
||||
}
|
||||
|
||||
@@ -32,12 +32,16 @@ namespace MQTTnet.Tests.Mockups
|
||||
{
|
||||
}
|
||||
|
||||
public TestEnvironment(TestContext testContext, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311)
|
||||
public TestEnvironment(
|
||||
TestContext testContext, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311, bool trackUnobservedTaskException = true)
|
||||
{
|
||||
_protocolVersion = protocolVersion;
|
||||
TestContext = testContext;
|
||||
|
||||
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
|
||||
if (trackUnobservedTaskException)
|
||||
{
|
||||
TaskScheduler.UnobservedTaskException += TrackUnobservedTaskException;
|
||||
}
|
||||
|
||||
ServerLogger.LogMessagePublished += (s, e) =>
|
||||
{
|
||||
@@ -102,10 +106,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public async Task<IMqttClient> ConnectClient(Action<MqttClientOptionsBuilder> configureOptions, TimeSpan timeout = default)
|
||||
{
|
||||
if (configureOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configureOptions));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(configureOptions);
|
||||
|
||||
// Start with initial default values.
|
||||
var optionsBuilder = ClientFactory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion).WithTcpServer("127.0.0.1", ServerPort);
|
||||
@@ -134,10 +135,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public async Task<IMqttClient> ConnectClient(MqttClientOptionsBuilder options, TimeSpan timeout = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
options = options.WithTcpServer("127.0.0.1", ServerPort);
|
||||
|
||||
@@ -160,10 +158,7 @@ namespace MQTTnet.Tests.Mockups
|
||||
|
||||
public async Task<IMqttClient> ConnectClient(MqttClientOptions options, TimeSpan timeout = default)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var client = CreateClient();
|
||||
|
||||
|
||||
@@ -40,10 +40,11 @@ public class MqttTcpChannel_Tests
|
||||
},
|
||||
ct.Token);
|
||||
|
||||
var remoteEndPoint = new DnsEndPoint("localhost", 50001);
|
||||
using var clientSocket = new CrossPlatformSocket(AddressFamily.InterNetwork, ProtocolType.Tcp);
|
||||
await clientSocket.ConnectAsync(new DnsEndPoint("localhost", 50001), CancellationToken.None);
|
||||
await clientSocket.ConnectAsync(remoteEndPoint, CancellationToken.None);
|
||||
|
||||
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), "test", null);
|
||||
var tcpChannel = new MqttTcpChannel(clientSocket.GetStream(), remoteEndPoint, null);
|
||||
|
||||
await Task.Delay(100, ct.Token);
|
||||
|
||||
|
||||
@@ -29,16 +29,17 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
|
||||
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword"));
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(eventArgs);
|
||||
|
||||
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event)));
|
||||
Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1"));
|
||||
Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1"));
|
||||
Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion);
|
||||
Assert.AreEqual("TheUser", eventArgs.UserName);
|
||||
Assert.AreEqual("ThePassword", eventArgs.Password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
|
||||
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser", "ThePassword"));
|
||||
await client.DisconnectAsync();
|
||||
|
||||
await LongTestDelay();
|
||||
@@ -64,8 +65,11 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.IsNotNull(eventArgs);
|
||||
|
||||
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event)));
|
||||
Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1"));
|
||||
Assert.IsTrue(eventArgs.RemoteEndPoint.ToString().Contains("127.0.0.1"));
|
||||
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
|
||||
|
||||
Assert.AreEqual("TheUser", eventArgs.UserName);
|
||||
Assert.AreEqual("ThePassword", eventArgs.Password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +87,7 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
|
||||
await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||
|
||||
await LongTestDelay();
|
||||
@@ -93,6 +97,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event)));
|
||||
Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic);
|
||||
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel);
|
||||
Assert.AreEqual("TheUser", eventArgs.UserName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +115,7 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
|
||||
await client.UnsubscribeAsync("The/Topic");
|
||||
|
||||
await LongTestDelay();
|
||||
@@ -119,6 +124,7 @@ namespace MQTTnet.Tests.Server
|
||||
|
||||
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event)));
|
||||
Assert.AreEqual("The/Topic", eventArgs.TopicFilter);
|
||||
Assert.AreEqual("TheUser", eventArgs.UserName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +142,7 @@ namespace MQTTnet.Tests.Server
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
var client = await testEnvironment.ConnectClient();
|
||||
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
|
||||
await client.PublishStringAsync("The_Topic", "The_Payload");
|
||||
|
||||
await LongTestDelay();
|
||||
@@ -146,6 +152,7 @@ namespace MQTTnet.Tests.Server
|
||||
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event)));
|
||||
Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic);
|
||||
Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString());
|
||||
Assert.AreEqual("TheUser", eventArgs.UserName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Server;
|
||||
using MQTTnet.Server.Exceptions;
|
||||
|
||||
namespace MQTTnet.Tests.Server
|
||||
{
|
||||
@@ -9,79 +13,443 @@ namespace MQTTnet.Tests.Server
|
||||
public sealed class Injection_Tests : BaseTestClass
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Inject_Application_Message_At_Session_Level()
|
||||
public async Task Enqueue_Application_Message_At_Session_Level()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
|
||||
var server = await testEnvironment.StartServer();
|
||||
var receiver1 = await testEnvironment.ConnectClient();
|
||||
var receiver2 = await testEnvironment.ConnectClient();
|
||||
var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1);
|
||||
var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2);
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
|
||||
await receiver1.SubscribeAsync("#");
|
||||
await receiver2.SubscribeAsync("#");
|
||||
|
||||
var message = new MqttApplicationMessageBuilder()
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.WithTopic("InjectedOne").Build();
|
||||
|
||||
var enqueued = clientStatus.TryEnqueueApplicationMessage(message, out var injectResult);
|
||||
|
||||
Assert.IsTrue(enqueued);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count);
|
||||
Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier);
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
|
||||
// The second receiver should NOT receive the message.
|
||||
Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false);
|
||||
|
||||
var server = await testEnvironment.StartServer(
|
||||
builder => builder
|
||||
.WithMaxPendingMessagesPerClient(1)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage));
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource();
|
||||
server.InterceptingOutboundPacketAsync += async args =>
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
var receiver1 = await testEnvironment.ConnectClient();
|
||||
var receiver2 = await testEnvironment.ConnectClient();
|
||||
var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1);
|
||||
var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2);
|
||||
// - The first message is dequeued normally and calls this delay
|
||||
// - The second message fills the outbound queue
|
||||
// - The third message overflows the outbound queue
|
||||
if (args.Packet is MqttPublishPacket)
|
||||
{
|
||||
firstMessageOutboundPacketInterceptedTcs.SetResult();
|
||||
await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
var firstMessageEvicted = false;
|
||||
var secondMessageEvicted = false;
|
||||
var thirdMessageEvicted = false;
|
||||
|
||||
await receiver1.SubscribeAsync("#");
|
||||
await receiver2.SubscribeAsync("#");
|
||||
server.QueuedApplicationMessageOverwrittenAsync += args =>
|
||||
{
|
||||
if (args.Packet is not MqttPublishPacket publishPacket)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
await clientStatus.EnqueueApplicationMessageAsync(new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build());
|
||||
switch (publishPacket.Topic)
|
||||
{
|
||||
case "InjectedOne":
|
||||
firstMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedTwo":
|
||||
secondMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedThree":
|
||||
thirdMessageEvicted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
await LongTestDelay();
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count);
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
await receiver.SubscribeAsync("#");
|
||||
|
||||
// The second receiver should NOT receive the message.
|
||||
Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count);
|
||||
}
|
||||
var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _);
|
||||
await firstMessageOutboundPacketInterceptedTcs.Task;
|
||||
|
||||
var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _);
|
||||
|
||||
var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _);
|
||||
|
||||
// Due to the DropNewMessage strategy the third message will not be enqueued.
|
||||
// As a result, no existing messages in the queue will be dropped (evicted).
|
||||
Assert.IsTrue(firstMessageEnqueued);
|
||||
Assert.IsTrue(secondMessageEnqueued);
|
||||
Assert.IsFalse(thirdMessageEnqueued);
|
||||
|
||||
Assert.IsFalse(firstMessageEvicted);
|
||||
Assert.IsFalse(secondMessageEvicted);
|
||||
Assert.IsFalse(thirdMessageEvicted);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public async Task Enqueue_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false);
|
||||
|
||||
var server = await testEnvironment.StartServer(
|
||||
builder => builder
|
||||
.WithMaxPendingMessagesPerClient(1)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage));
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource();
|
||||
server.InterceptingOutboundPacketAsync += async args =>
|
||||
{
|
||||
// - The first message is dequeued normally and calls this delay
|
||||
// - The second message fills the outbound queue
|
||||
// - The third message overflows the outbound queue
|
||||
if (args.Packet is MqttPublishPacket)
|
||||
{
|
||||
firstMessageOutboundPacketInterceptedTcs.SetResult();
|
||||
await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
var firstMessageEvicted = false;
|
||||
var secondMessageEvicted = false;
|
||||
var thirdMessageEvicted = false;
|
||||
|
||||
server.QueuedApplicationMessageOverwrittenAsync += args =>
|
||||
{
|
||||
if (args.Packet is not MqttPublishPacket publishPacket)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
switch (publishPacket.Topic)
|
||||
{
|
||||
case "InjectedOne":
|
||||
firstMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedTwo":
|
||||
secondMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedThree":
|
||||
thirdMessageEvicted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
await receiver.SubscribeAsync("#");
|
||||
|
||||
var firstMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build(), out _);
|
||||
await firstMessageOutboundPacketInterceptedTcs.Task;
|
||||
|
||||
var secondMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build(), out _);
|
||||
|
||||
var thirdMessageEnqueued = clientStatus.TryEnqueueApplicationMessage(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build(), out _);
|
||||
|
||||
// Due to the DropOldestQueuedMessage strategy, all messages will be enqueued initially.
|
||||
// But the second message will eventually be dropped (evicted) to make room for the third one.
|
||||
Assert.IsTrue(firstMessageEnqueued);
|
||||
Assert.IsTrue(secondMessageEnqueued);
|
||||
Assert.IsTrue(thirdMessageEnqueued);
|
||||
|
||||
Assert.IsFalse(firstMessageEvicted);
|
||||
Assert.IsTrue(secondMessageEvicted);
|
||||
Assert.IsFalse(thirdMessageEvicted);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deliver_Application_Message_At_Session_Level()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
|
||||
var server = await testEnvironment.StartServer();
|
||||
var receiver1 = await testEnvironment.ConnectClient();
|
||||
var receiver2 = await testEnvironment.ConnectClient();
|
||||
var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1);
|
||||
var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2);
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
|
||||
await receiver1.SubscribeAsync("#");
|
||||
await receiver2.SubscribeAsync("#");
|
||||
|
||||
var mqttApplicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic("InjectedOne")
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.Build();
|
||||
var injectResult = await clientStatus.DeliverApplicationMessageAsync(mqttApplicationMessage);
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count);
|
||||
Assert.AreEqual(injectResult.PacketIdentifier, messageReceivedHandler1.ReceivedEventArgs[0].PacketIdentifier);
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
|
||||
// The second receiver should NOT receive the message.
|
||||
Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropNewMessageStrategy()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
|
||||
var server = await testEnvironment.StartServer(
|
||||
builder => builder
|
||||
.WithMaxPendingMessagesPerClient(1)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage));
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource();
|
||||
server.InterceptingOutboundPacketAsync += async args =>
|
||||
{
|
||||
// - The first message is dequeued normally and calls this delay
|
||||
// - The second message fills the outbound queue
|
||||
// - The third message overflows the outbound queue
|
||||
if (args.Packet is MqttPublishPacket)
|
||||
{
|
||||
firstMessageOutboundPacketInterceptedTcs.SetResult();
|
||||
await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
var firstMessageEvicted = false;
|
||||
var secondMessageEvicted = false;
|
||||
var thirdMessageEvicted = false;
|
||||
|
||||
server.QueuedApplicationMessageOverwrittenAsync += args =>
|
||||
{
|
||||
if (args.Packet is not MqttPublishPacket publishPacket)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
switch (publishPacket.Topic)
|
||||
{
|
||||
case "InjectedOne":
|
||||
firstMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedTwo":
|
||||
secondMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedThree":
|
||||
thirdMessageEvicted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
await receiver.SubscribeAsync("#");
|
||||
|
||||
var firstMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build()));
|
||||
await LongTestDelay();
|
||||
await firstMessageOutboundPacketInterceptedTcs.Task;
|
||||
|
||||
var secondMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build()));
|
||||
await LongTestDelay();
|
||||
|
||||
var thirdMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build()));
|
||||
await LongTestDelay();
|
||||
|
||||
Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask);
|
||||
|
||||
// Due to the DropNewMessage strategy the third message delivery will fail.
|
||||
// As a result, no existing messages in the queue will be dropped (evicted).
|
||||
Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation);
|
||||
Assert.AreEqual(secondMessageTask.Status, TaskStatus.WaitingForActivation);
|
||||
Assert.AreEqual(thirdMessageTask.Status, TaskStatus.Faulted);
|
||||
Assert.IsTrue(thirdMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException);
|
||||
|
||||
Assert.IsFalse(firstMessageEvicted);
|
||||
Assert.IsFalse(secondMessageEvicted);
|
||||
Assert.IsFalse(thirdMessageEvicted);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Deliver_Application_Message_At_Session_Level_QueueOverflow_DropOldestQueuedMessageStrategy()
|
||||
{
|
||||
using var testEnvironment = CreateTestEnvironment(trackUnobservedTaskException: false);
|
||||
|
||||
var server = await testEnvironment.StartServer(
|
||||
builder => builder
|
||||
.WithMaxPendingMessagesPerClient(1)
|
||||
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropOldestQueuedMessage));
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
var firstMessageOutboundPacketInterceptedTcs = new TaskCompletionSource();
|
||||
server.InterceptingOutboundPacketAsync += async args =>
|
||||
{
|
||||
// - The first message is dequeued normally and calls this delay
|
||||
// - The second message fills the outbound queue
|
||||
// - The third message overflows the outbound queue
|
||||
if (args.Packet is MqttPublishPacket)
|
||||
{
|
||||
firstMessageOutboundPacketInterceptedTcs.SetResult();
|
||||
await Task.Delay(TimeSpan.FromDays(1), args.CancellationToken);
|
||||
}
|
||||
};
|
||||
|
||||
var firstMessageEvicted = false;
|
||||
var secondMessageEvicted = false;
|
||||
var thirdMessageEvicted = false;
|
||||
|
||||
server.QueuedApplicationMessageOverwrittenAsync += args =>
|
||||
{
|
||||
if (args.Packet is not MqttPublishPacket publishPacket)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
switch (publishPacket.Topic)
|
||||
{
|
||||
case "InjectedOne":
|
||||
firstMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedTwo":
|
||||
secondMessageEvicted = true;
|
||||
break;
|
||||
case "InjectedThree":
|
||||
thirdMessageEvicted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
var status = await server.GetSessionsAsync();
|
||||
var clientStatus = status[0];
|
||||
await receiver.SubscribeAsync("#");
|
||||
|
||||
var firstMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build()));
|
||||
await LongTestDelay();
|
||||
await firstMessageOutboundPacketInterceptedTcs.Task;
|
||||
|
||||
var secondMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedTwo").Build()));
|
||||
await LongTestDelay();
|
||||
|
||||
var thirdMessageTask = Task.Run(
|
||||
() => clientStatus.DeliverApplicationMessageAsync(
|
||||
new MqttApplicationMessageBuilder().WithTopic("InjectedThree").Build()));
|
||||
await LongTestDelay();
|
||||
|
||||
Task.WaitAny(firstMessageTask, secondMessageTask, thirdMessageTask);
|
||||
|
||||
// Due to the DropOldestQueuedMessage strategy, the second message delivery will fail
|
||||
// to make room for the third one.
|
||||
Assert.AreEqual(firstMessageTask.Status, TaskStatus.WaitingForActivation);
|
||||
Assert.AreEqual(secondMessageTask.Status, TaskStatus.Faulted);
|
||||
Assert.IsTrue(secondMessageTask.Exception?.InnerException is MqttPendingMessagesOverflowException);
|
||||
Assert.AreEqual(thirdMessageTask.Status, TaskStatus.WaitingForActivation);
|
||||
|
||||
Assert.IsFalse(firstMessageEvicted);
|
||||
Assert.IsTrue(secondMessageEvicted);
|
||||
Assert.IsFalse(thirdMessageEvicted);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Inject_ApplicationMessage_At_Server_Level()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
var receiver = await testEnvironment.ConnectClient();
|
||||
|
||||
await receiver.SubscribeAsync("#");
|
||||
var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
await receiver.SubscribeAsync("#");
|
||||
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
|
||||
await LongTestDelay();
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
|
||||
Assert.AreEqual(1, messageReceivedHandler.ReceivedEventArgs.Count);
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
}
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.AreEqual(1, messageReceivedHandler.ReceivedEventArgs.Count);
|
||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Intercept_Injected_Application_Message()
|
||||
{
|
||||
using (var testEnvironment = CreateTestEnvironment())
|
||||
using var testEnvironment = CreateTestEnvironment();
|
||||
|
||||
var server = await testEnvironment.StartServer();
|
||||
|
||||
MqttApplicationMessage interceptedMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
var server = await testEnvironment.StartServer();
|
||||
interceptedMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
|
||||
MqttApplicationMessage interceptedMessage = null;
|
||||
server.InterceptingPublishAsync += eventArgs =>
|
||||
{
|
||||
interceptedMessage = eventArgs.ApplicationMessage;
|
||||
return CompletedTask.Instance;
|
||||
};
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
|
||||
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||
await LongTestDelay();
|
||||
|
||||
await LongTestDelay();
|
||||
|
||||
Assert.IsNotNull(interceptedMessage);
|
||||
}
|
||||
Assert.IsNotNull(interceptedMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@ namespace MQTTnet.Tests.Server
|
||||
}
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[TestMethod]
|
||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||
public async Task Retry_If_Not_PubAck(MqttQualityOfServiceLevel qos)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MQTTnet.Tests.Server
|
||||
[TestClass]
|
||||
public sealed class Subscribe_Tests : BaseTestClass
|
||||
{
|
||||
[DataTestMethod]
|
||||
[TestMethod]
|
||||
[DataRow("A", "A", true)]
|
||||
[DataRow("A", "B", false)]
|
||||
[DataRow("A", "#", true)]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,7 +19,8 @@ public interface IMqttChannelAdapter : IDisposable
|
||||
long BytesSent { get; }
|
||||
|
||||
X509Certificate2 ClientCertificate { get; }
|
||||
string Endpoint { get; }
|
||||
|
||||
EndPoint RemoteEndPoint { get; }
|
||||
|
||||
bool IsSecureConnection { get; }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
@@ -38,10 +39,7 @@ public sealed class MqttChannelAdapter : Disposable, IMqttChannelAdapter
|
||||
|
||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttChannelAdapter));
|
||||
}
|
||||
@@ -54,7 +52,7 @@ public sealed class MqttChannelAdapter : Disposable, IMqttChannelAdapter
|
||||
|
||||
public X509Certificate2 ClientCertificate => _channel.ClientCertificate;
|
||||
|
||||
public string Endpoint => _channel.Endpoint;
|
||||
public EndPoint RemoteEndPoint => _channel.RemoteEndPoint;
|
||||
|
||||
public bool IsSecureConnection => _channel.IsSecureConnection;
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@ public sealed class MqttPacketInspector
|
||||
{
|
||||
_asyncEvent = asyncEvent ?? throw new ArgumentNullException(nameof(asyncEvent));
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_logger = logger.WithSource(nameof(MqttPacketInspector));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -13,7 +14,7 @@ namespace MQTTnet.Channel;
|
||||
public interface IMqttChannel : IDisposable
|
||||
{
|
||||
X509Certificate2 ClientCertificate { get; }
|
||||
string Endpoint { get; }
|
||||
EndPoint RemoteEndPoint { get; }
|
||||
|
||||
bool IsSecureConnection { get; }
|
||||
|
||||
|
||||
@@ -14,10 +14,7 @@ public sealed class MqttClientConnectResultFactory
|
||||
{
|
||||
public MqttClientConnectResult Create(MqttConnAckPacket connAckPacket, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (connAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connAckPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(connAckPacket);
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
@@ -68,10 +65,8 @@ public sealed class MqttClientConnectResultFactory
|
||||
|
||||
static MqttClientConnectResult CreateForMqtt311(MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
if (connAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connAckPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(connAckPacket);
|
||||
|
||||
|
||||
return new MqttClientConnectResult
|
||||
{
|
||||
@@ -84,10 +79,7 @@ public sealed class MqttClientConnectResultFactory
|
||||
|
||||
static MqttClientConnectResult CreateForMqtt500(MqttConnAckPacket connAckPacket)
|
||||
{
|
||||
if (connAckPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connAckPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(connAckPacket);
|
||||
|
||||
return new MqttClientConnectResult
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace MQTTnet.Diagnostics.Logger
|
||||
/*
|
||||
* The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
|
||||
*/
|
||||
|
||||
|
||||
public static class MqttNetSourceLoggerExtensions
|
||||
{
|
||||
public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
||||
@@ -91,7 +91,7 @@ namespace MQTTnet.Diagnostics.Logger
|
||||
|
||||
logger.Publish(logLevel, message, new object[] { parameter1 }, exception);
|
||||
}
|
||||
|
||||
|
||||
public static void Publish<TParameter1, TParameter2>(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||
{
|
||||
if (!logger.IsEnabled)
|
||||
@@ -209,10 +209,7 @@ namespace MQTTnet.Diagnostics.Logger
|
||||
|
||||
public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
return new MqttNetSourceLogger(logger, source);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttClientDisconnectOptionsValidator
|
||||
{
|
||||
public static void ThrowIfNotSupported(MqttClientDisconnectOptions options, MqttProtocolVersion protocolVersion)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (protocolVersion == MqttProtocolVersion.V500)
|
||||
{
|
||||
|
||||
-66
@@ -1,66 +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)
|
||||
{
|
||||
if (authPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(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; }
|
||||
}
|
||||
@@ -11,10 +11,7 @@ namespace MQTTnet.Formatter
|
||||
{
|
||||
public static MqttApplicationMessage Create(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(publishPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(publishPacket);
|
||||
|
||||
return new MqttApplicationMessage
|
||||
{
|
||||
|
||||
@@ -105,10 +105,7 @@ namespace MQTTnet.Formatter
|
||||
|
||||
public void Write(MqttBufferWriter propertyWriter)
|
||||
{
|
||||
if (propertyWriter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(propertyWriter));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(propertyWriter);
|
||||
|
||||
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
|
||||
}
|
||||
@@ -140,10 +137,7 @@ namespace MQTTnet.Formatter
|
||||
|
||||
public void WriteBinary(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(buffer);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
@@ -292,7 +286,7 @@ namespace MQTTnet.Formatter
|
||||
|
||||
if (_position > Length)
|
||||
{
|
||||
// Also extend the position because we reached the end of the
|
||||
// Also extend the position because we reached the end of the
|
||||
// pre allocated buffer.
|
||||
Length = _position;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttConnectPacketFactory
|
||||
{
|
||||
public static MqttConnectPacket Create(MqttClientOptions clientOptions)
|
||||
{
|
||||
if (clientOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientOptions));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientOptions);
|
||||
|
||||
var connectPacket = new MqttConnectPacket
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttPubAckPacketFactory
|
||||
{
|
||||
public static MqttPubAckPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs)
|
||||
{
|
||||
if (applicationMessageReceivedEventArgs == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessageReceivedEventArgs);
|
||||
|
||||
var pubAckPacket = new MqttPubAckPacket
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttPubCompPacketFactory
|
||||
{
|
||||
public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode)
|
||||
{
|
||||
if (pubRelPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pubRelPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(pubRelPacket);
|
||||
|
||||
return new MqttPubCompPacket
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttPubRecPacketFactory
|
||||
{
|
||||
public static MqttPubRecPacket Create(MqttApplicationMessageReceivedEventArgs applicationMessageReceivedEventArgs)
|
||||
{
|
||||
if (applicationMessageReceivedEventArgs == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessageReceivedEventArgs);
|
||||
|
||||
var pubRecPacket = Create(applicationMessageReceivedEventArgs.PublishPacket, applicationMessageReceivedEventArgs.ReasonCode);
|
||||
pubRecPacket.UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties;
|
||||
|
||||
@@ -12,10 +12,7 @@ public static class MqttPubRelPacketFactory
|
||||
{
|
||||
public static MqttPubRelPacket Create(MqttPubRecPacket pubRecPacket, MqttApplicationMessageReceivedReasonCode reasonCode)
|
||||
{
|
||||
if (pubRecPacket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(pubRecPacket));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(pubRecPacket);
|
||||
|
||||
return new MqttPubRelPacket
|
||||
{
|
||||
|
||||
@@ -12,10 +12,7 @@ namespace MQTTnet.Formatter
|
||||
{
|
||||
public static MqttPublishPacket Create(MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(applicationMessage));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(applicationMessage);
|
||||
|
||||
// Copy all values to their matching counterparts.
|
||||
// The not supported values in MQTT 3.1.1 are not serialized (excluded) later.
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttSubscribePacketFactory
|
||||
{
|
||||
public static MqttSubscribePacket Create(MqttClientSubscribeOptions clientSubscribeOptions)
|
||||
{
|
||||
if (clientSubscribeOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientSubscribeOptions));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientSubscribeOptions);
|
||||
|
||||
var packet = new MqttSubscribePacket
|
||||
{
|
||||
|
||||
@@ -11,10 +11,7 @@ public static class MqttUnsubscribePacketFactory
|
||||
{
|
||||
public static MqttUnsubscribePacket Create(MqttClientUnsubscribeOptions clientUnsubscribeOptions)
|
||||
{
|
||||
if (clientUnsubscribeOptions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(clientUnsubscribeOptions));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(clientUnsubscribeOptions);
|
||||
|
||||
var packet = new MqttUnsubscribePacket
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -18,9 +18,9 @@ namespace MQTTnet.Formatter.V3
|
||||
{
|
||||
const int FixedHeaderSize = 1;
|
||||
|
||||
static readonly MqttDisconnectPacket DisconnectPacket = new MqttDisconnectPacket();
|
||||
static readonly MqttDisconnectPacket DisconnectPacket = new();
|
||||
|
||||
readonly MqttBufferReader _bufferReader = new MqttBufferReader();
|
||||
readonly MqttBufferReader _bufferReader = new();
|
||||
readonly MqttBufferWriter _bufferWriter;
|
||||
readonly MqttProtocolVersion _mqttProtocolVersion;
|
||||
|
||||
@@ -68,10 +68,8 @@ namespace MQTTnet.Formatter.V3
|
||||
{
|
||||
return DecodeConnAckPacketV311(receivedMqttPacket.Body);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DecodeConnAckPacket(receivedMqttPacket.Body);
|
||||
}
|
||||
|
||||
return DecodeConnAckPacket(receivedMqttPacket.Body);
|
||||
case MqttControlPacketType.Disconnect:
|
||||
return DisconnectPacket;
|
||||
|
||||
@@ -91,10 +89,7 @@ namespace MQTTnet.Formatter.V3
|
||||
|
||||
public MqttPacketBuffer Encode(MqttPacket packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(packet));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(packet);
|
||||
|
||||
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes)
|
||||
_bufferWriter.Reset(5);
|
||||
@@ -109,10 +104,6 @@ namespace MQTTnet.Formatter.V3
|
||||
payload = publishPacket.Payload;
|
||||
remainingLength += (uint)payload.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
publishPacket = null;
|
||||
}
|
||||
|
||||
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
||||
|
||||
@@ -484,7 +475,16 @@ namespace MQTTnet.Formatter.V3
|
||||
ValidateConnectPacket(packet);
|
||||
|
||||
bufferWriter.WriteString("MQTT");
|
||||
bufferWriter.WriteByte(4); // 3.1.2.2 Protocol Level 4
|
||||
|
||||
// 3.1.2.2 Protocol Level 4
|
||||
var protocolVersion = 4;
|
||||
|
||||
if (packet.TryPrivate)
|
||||
{
|
||||
protocolVersion |= 0x80;
|
||||
}
|
||||
|
||||
bufferWriter.WriteByte((byte)protocolVersion);
|
||||
|
||||
byte connectFlags = 0x0;
|
||||
if (packet.CleanSession)
|
||||
@@ -555,19 +555,15 @@ namespace MQTTnet.Formatter.V3
|
||||
{
|
||||
return EncodeConnectPacketV311(connectPacket, bufferWriter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return EncodeConnectPacket(connectPacket, bufferWriter);
|
||||
}
|
||||
|
||||
return EncodeConnectPacket(connectPacket, bufferWriter);
|
||||
case MqttConnAckPacket connAckPacket:
|
||||
if (_mqttProtocolVersion == MqttProtocolVersion.V311)
|
||||
{
|
||||
return EncodeConnAckPacketV311(connAckPacket, bufferWriter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return EncodeConnAckPacket(connAckPacket, bufferWriter);
|
||||
}
|
||||
|
||||
return EncodeConnAckPacket(connAckPacket, bufferWriter);
|
||||
case MqttDisconnectPacket _:
|
||||
return EncodeEmptyPacket(MqttControlPacketType.Disconnect);
|
||||
case MqttPingReqPacket _:
|
||||
@@ -797,10 +793,7 @@ namespace MQTTnet.Formatter.V3
|
||||
|
||||
void ValidateConnectPacket(MqttConnectPacket packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(packet));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(packet);
|
||||
|
||||
if (string.IsNullOrEmpty(packet.ClientId) && !packet.CleanSession)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -25,10 +25,7 @@ public sealed class MqttV5PacketEncoder
|
||||
|
||||
public MqttPacketBuffer Encode(MqttPacket packet)
|
||||
{
|
||||
if (packet == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(packet));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(packet);
|
||||
|
||||
// Leave enough head space for max header size (fixed + 4 variable remaining length = 5 bytes)
|
||||
const int ReservedHeaderSize = 5;
|
||||
|
||||
@@ -221,7 +221,7 @@ namespace MQTTnet.Formatter.V5
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
// Absence of the flag means it is supported!
|
||||
// Absence of the flag means it is supported!
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -230,10 +230,7 @@ namespace MQTTnet.Formatter.V5
|
||||
|
||||
public void WriteTo(MqttBufferWriter target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(target);
|
||||
|
||||
target.WriteVariableByteInteger((uint)_bufferWriter.Length);
|
||||
target.Write(_bufferWriter);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user