Compare commits

...
24 Commits
Author SHA1 Message Date
Lucas Tandchristian 948c7c2b69 Corrected Cancellation Source for KeepAlive Messages (#2129)
* Corrected Cancellation Source for KeepAlive Messages

* Use half of the keep alive value for the ping timeout

* Update release notes

* Update ReleaseNotes.md

* Update ReleaseNotes.md

* Update ReleaseNotes.md

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-01-02 12:17:05 +01:00
christian 1cdc343e6d Update release notes 2025-01-02 12:11:57 +01:00
27771f0146 Improve session message injection (#2117)
* Improve session message injection methods

* Add more tests for session injection

* Change return type from MqttPublishPacket to MqttPacketWithIdentifier

* Use dedicated type for MattApplicationMessage injection result

* Code review minor fixes

* Apply project code style

* Update release notes

---------

Co-authored-by: Anton Smolkov <anton.smolkov@idp.zyfra.com>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-01-02 11:51:46 +01:00
christian e7de2b913a Increase version 2025-01-02 11:34:14 +01:00
christian 3dd3279656 Fix missing release notes in nuget package 2025-01-02 11:34:08 +01:00
christian c5934bf18b Reset release notes 2025-01-02 11:22:19 +01:00
christian 7715bf37dc Remove package suffix for final release version 2024-12-30 11:54:00 +01:00
Christian 9dbe1e4b12 Fix support for enhanced authentication (#2132)
* Implement enhanced authentication for client and server.

* Update samples

* Add abstraction layer

* Merge master

* Use dedicated class for public API.

* Update release notes

* Rename methods to fit naming scheme

* Fix unit tests and samples
2024-12-25 14:17:20 +01:00
christian f13b3eb6bb Update release notes 2024-12-23 12:17:00 +01:00
Anton SmolkovandAnton Smolkov 8eb884fa5d Add ability to get session from server by client id (#2131)
Co-authored-by: Anton Smolkov <anton.smolkov@idp.zyfra.com>
2024-12-23 12:05:09 +01:00
Victornorandchristian 9f81a2bb71 Add deflate compressions options to MqttClientWebSocketOptionsBuilder (#2127)
* fix: unused tryPrivate parameter in WithTryPrivate

* Fix wrong usage of try private

* feat: add option to provide deflate compression options to WebSocketOptionsBuilder

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2024-12-23 11:21:46 +01:00
老九andchristian e2f8d0f0ac Added UserName property to various Server EventArgs. (#2123)
* Added UserName property to various Server EventArgs.

* InjectedMqttApplicationMessage: Add SenderUserName property.

* Reformat code

* Reformat code

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2024-12-10 19:44:38 +01:00
Victornorandchristian 981ea02149 Unused parameter in WithTryPrivate causing client to send incorrect version byte in header. (#2125)
* fix: unused tryPrivate parameter in WithTryPrivate

* Fix wrong usage of try private

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2024-12-10 19:37:29 +01:00
Christian 8566fd5395 Replace all _DataTestMethod_ (#2119)
* Replace all DataTestMethod with TestMethod.

* Update test nugets
2024-12-03 11:40:06 +01:00
老九andchristian 8089c6bdbc Added EndPoint RemoteEndPoint {get;} property to channel (#2111)
* Added EndPoint RemoteEndPoint{get;} property

* Reformat code

* Reformat code

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2024-12-02 11:52:24 +01:00
christian ef5b6c9b32 Merge remote-tracking branch 'origin/master' 2024-12-02 11:22:11 +01:00
christian 4aa4a12e47 Update release notes 2024-12-02 10:40:05 +01:00
老九 6bf2b4eaca Cannot set any properties of tcpOptions when with WebSocketServer. (#2118) 2024-12-02 10:24:03 +01:00
老九 6ef6b57064 Improve the compatibility of MqttClientOptionsBuilder.WithConnectionUri() (#2107)
* Improve the compatibility of MqttClientOptionsBuilder.WithConnectionUri() method.

* Force UseTls to true for secure schemes.

* Add support for UnixDomainSocket.
2024-12-01 14:10:58 +01:00
老九 7d4027958c Set the default value of SslProtocol to SslProtocols.None. (#2106)
* Set the default value of SslProtocol to SslProtocols.None.

* Add summary of SslProtocol.
2024-12-01 14:06:19 +01:00
老九 bdb74642c2 MqttClientFactory: Fix CreateLowLevelMqttClient not using method parameters (#2110)
* Fix CreateLowLevelMqttClient not using method parameters.

* Remove UseClientAdapterFactory()
2024-11-30 13:46:36 +01:00
Andrea Vouk 63d85184e1 Convert null checks to modern ThrowIfNull checks (#2089)
Convert null check to modern ThrowIfNull checks
2024-10-26 11:41:58 +02:00
Christian 0506857f0d Set max pending mesages per client to 1000 (#2096)
Set max pending mesages per client to 1000.
2024-10-26 11:41:25 +02:00
christian 6417baa081 Fix nuget publish 2024-10-22 11:08:29 +02:00
140 changed files with 2345 additions and 1859 deletions
+4 -3
View File
@@ -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
+477 -418
View File
@@ -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; }
}
@@ -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; }
}
@@ -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));
}
+13 -4
View File
@@ -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;
}
+20 -52
View File
@@ -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();
+10 -45
View File
@@ -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);
+10 -17
View File
@@ -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;
+3 -1
View File
@@ -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.
+4 -3
View File
@@ -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);
}
}
+4 -3
View File
@@ -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();
+3 -2
View File
@@ -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);
+14 -7
View File
@@ -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);
}
}
+412 -44
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -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; }
+3 -5
View File
@@ -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));
}
+2 -1
View File
@@ -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)
{
@@ -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
{
+3 -9
View File
@@ -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