Compare commits

..
Author SHA1 Message Date
christian 37631730e0 Enable nullable 2025-10-20 11:29:24 +02:00
Christian 5ebc5fcb7e Call enqueue interceptor for retained messages (fix) (#2212)
* Call the event handler also for retained messages

* Update release notes

* Add Unit Test
2025-10-19 21:21:47 +02:00
Christian 51979fa5a4 Call enqueue interceptor for retained messages (#2211)
* Call the event handler also for retained messages

* Update release notes
2025-10-19 19:14:33 +02:00
Christian 7ac7f256ef Update nugets (#2210)
* Upate nugets, fix breaking changes

* Fix CI build
2025-10-17 21:29:46 +02:00
e7ccd897b0 Use License file from code repo (#2197)
* Use License file from code repo

* Configure all relevant projects

* Update release notes

* Restore release notes

---------

Co-authored-by: Jens Nordenbro <jens.nordenbro@beijerelectronics.com>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-10-17 19:28:45 +02:00
Christian d5d72ac37d Enable support for AOT compilation (#2209)
* Update project files

* Update release notes
2025-10-17 19:06:28 +02:00
Christian a5ef10fddb Restore strong name (#2208)
* Restore strong name certificate

* Migrate strong name certificate loading to linux.

* Fix wrong path

* Restore version, update release notes
2025-10-17 19:05:20 +02:00
zhaowgitandChristian c97bd82334 Issues #2091 Improve the performance of RetainedMessages without subs… (#2093)
* Issues #2091 Improve the performance of RetainedMessages without subscribing to RetainedMessageChangedEvent.

* Fix build errors

* Update release notes

---------

Co-authored-by: Christian <6939810+chkr1011@users.noreply.github.com>
2025-10-17 19:04:11 +02:00
christian 75ebf116cf Merge remote-tracking branch 'origin/master' 2025-09-14 10:47:46 +02:00
christian 2f7f55e3f5 Update version 2025-09-14 10:47:32 +02:00
Christian e43e7f8e36 Use latest language features (#2176)
* Make use of language features line AsyncDispose, init etc.

* Fix build errors

* Improve Unit Tests

* Improve Unit Tests

* Improve Unit Tests

* Update samples

* Remove obsolete benchmarks

* Code cleanup

* Improve unit tests

* Improve Unit Tests

* Fix Unit Tests

* Improve Unit Tests

* Improve Unit Tests

* Restore unit tests

* Exclude load tests from unit tests
2025-08-30 10:52:59 +02:00
timyhac 247988e633 Remove "MQTTv5 only" in documentation comments for MqttClientConnectResult.IsSessionPresent and MqttClientConnectResult.ResultCode (#2166)
* Remove "MQTTv5" from documentation comments for `IsSessionPresent`

 As far as I can tell, this is available for MQTT v3.1.1

References:
* [OASIS Standard](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035)
* [MQTTnet source code](https://github.com/dotnet/MQTTnet/blob/93a9d8d1dbe8dde2b7d79a37c873fae13b4f4797/Source/MQTTnet/Connecting/MqttClientConnectResultFactory.cs#L75)

* Remove "MQTTv5 only" for `ResultCode`
2025-03-29 19:11:53 +01:00
Youssef Victor 93a9d8d1db Move to MTP (#2163) 2025-03-02 11:59:33 +01:00
Christian 24c418cf43 Update README.md 2025-01-07 18:57:58 +01:00
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
403 changed files with 23274 additions and 24144 deletions
+13 -5
View File
@@ -3,8 +3,8 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "5.0.0.${{github.run_number}}" VERSION: "5.1.0.${{github.run_number}}"
PACKAGE_SUFFIX: "-RC" PACKAGE_SUFFIX: ""
jobs: jobs:
build: build:
@@ -21,8 +21,15 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Load strong name certificate
run: |
echo "$SNC_BASE64" | base64 --decode > "${{ github.workspace }}/certificate.snk"
shell: bash
env:
SNC_BASE64: ${{ secrets.SNC_BASE64 }}
- name: Build package - name: Build package
run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }} run: dotnet build MQTTnet.sln --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }} /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=${{ github.workspace }}/certificate.snk
- name: Upload nuget packages - name: Upload nuget packages
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@@ -46,7 +53,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Execute tests - name: Execute tests
run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj run: dotnet test --framework net8.0 --configuration Release Source/MQTTnet.Tests/MQTTnet.Tests.csproj
sign: sign:
needs: build needs: build
@@ -107,7 +114,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 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: 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] needs: [sign, test]
runs-on: ubuntu-latest runs-on: ubuntu-latest
+9
View File
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<EnableMSTestRunner>true</EnableMSTestRunner>
</PropertyGroup>
</Project>
+4
View File
@@ -1,4 +1,8 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToSwitchStatement/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=InconsistentNaming/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UncatchableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UnthrowableException/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
+2 -2
View File
@@ -3,9 +3,9 @@
<br/> <br/>
</p> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://img.shields.io/nuget/dt/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml) [![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet) [![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg) ![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://raw.githubusercontent.com/dotnet/MQTTnet/master/LICENSE)
+477 -418
View File
@@ -8,431 +8,17 @@
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers; using MQTTnet.Samples.Helpers;
using MQTTnet.Server;
using MQTTnet.Server.EnhancedAuthentication;
namespace MQTTnet.Samples.Client; namespace MQTTnet.Samples.Client;
public static class Client_Connection_Samples 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 = @" const string mosquitto_org = @"
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
@@ -460,4 +46,477 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE----- -----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(mqttClientOptions);
}
};
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);
}
}
} }
+34 -38
View File
@@ -23,25 +23,23 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder() var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("19.5") .WithPayload("19.5")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync(); await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published."); Console.WriteLine("MQTT application message is published.");
}
} }
public static async Task Publish_Multiple_Application_Messages() public static async Task Publish_Multiple_Application_Messages()
@@ -54,38 +52,36 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder()
var mqttClientOptions = new MqttClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder() var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("19.5") .WithPayload("19.5")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder() applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("20.0") .WithPayload("20.0")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder() applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room") .WithTopic("samples/temperature/living_room")
.WithPayload("21.0") .WithPayload("21.0")
.Build(); .Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None); await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync(); await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published."); Console.WriteLine("MQTT application message is published.");
}
} }
} }
+57 -65
View File
@@ -26,32 +26,30 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
// Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all
// received messages get lost.
mqttClient.ApplicationMessageReceivedAsync += e =>
{ {
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); Console.WriteLine("Received application message.");
e.DumpToConsole();
// Setup message handling before connecting so that queued messages return Task.CompletedTask;
// are also handled properly. When there is no event handler attached all };
// received messages get lost.
mqttClient.ApplicationMessageReceivedAsync += e =>
{
Console.WriteLine("Received application message.");
e.DumpToConsole();
return Task.CompletedTask; await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
};
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build();
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build(); await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); Console.WriteLine("MQTT client subscribed to topic.");
Console.WriteLine("MQTT client subscribed to topic."); Console.WriteLine("Press enter to exit.");
Console.ReadLine();
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
} }
public static async Task Send_Responses() public static async Task Send_Responses()
@@ -62,36 +60,34 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
{ {
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args) // Do some work with the message...
{
// Do some work with the message...
// Now respond to the broker with a reason code other than success. // Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError; args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!"; args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5! // User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data")); args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// Now the broker will resend the message again. // Now the broker will resend the message again.
return Task.CompletedTask; return Task.CompletedTask;
}; };
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic."); Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); response.DumpToConsole();
}
} }
public static async Task Subscribe_Multiple_Topics() public static async Task Subscribe_Multiple_Topics()
@@ -102,27 +98,25 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Create the subscribe options including several topics with different options. // Create the subscribe options including several topics with different options.
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic. // It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicTemplate(sampleTemplate.WithParameter("id", "1")) .WithTopicTemplate(sampleTemplate.WithParameter("id", "1"))
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) .WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true)
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) .WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
.Build(); .Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics."); Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); response.DumpToConsole();
}
} }
public static async Task Subscribe_Topic() public static async Task Subscribe_Topic()
@@ -133,21 +127,19 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build(); var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic."); Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing. // The response contains additional data sent by the server after subscribing.
response.DumpToConsole(); response.DumpToConsole();
}
} }
static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient) static void ConcurrentProcessingDisableAutoAcknowledge(CancellationToken shutdownToken, IMqttClient mqttClient)
+13 -17
View File
@@ -26,17 +26,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected."); Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
public static async Task Use_Event_Logger() public static async Task Use_Event_Logger()
@@ -50,7 +48,7 @@ public static class Logger_Samples
// The logger ID is optional but can be set do distinguish different logger instances. // The logger ID is optional but can be set do distinguish different logger instances.
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger"); var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
mqttEventLogger.LogMessagePublished += (sender, args) => mqttEventLogger.LogMessagePublished += (_, args) =>
{ {
var output = new StringBuilder(); var output = new StringBuilder();
output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}"); output.AppendLine($">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
@@ -68,17 +66,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected."); Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
sealed class MyLogger : IMqttNetLogger sealed class MyLogger : IMqttNetLogger
@@ -20,23 +20,21 @@ public static class PackageInspection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{ var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder() .WithTcpServer("broker.hivemq.com")
.WithTcpServer("broker.hivemq.com") .Build();
.Build();
mqttClient.InspectPacketAsync += OnInspectPacket; mqttClient.InspectPacketAsync += OnInspectPacket;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected."); Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder() var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build(); .Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
} }
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
+1
View File
@@ -15,6 +15,7 @@
<NuGetAudit>true</NuGetAudit> <NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel> <NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel> <AnalysisLevel>latest-Recommended</AnalysisLevel>
<LangVersion>default</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+13 -15
View File
@@ -26,23 +26,21 @@ public static class RpcClient_Samples
// The RPC client is an addon for the existing client. So we need a regular client // The RPC client is an addon for the existing client. So we need a regular client
// which is wrapped later. // which is wrapped later.
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions);
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{ {
var mqttClientOptions = new MqttClientOptionsBuilder() // Access to a fully featured application message is not supported for RPC calls!
.WithTcpServer("broker.hivemq.com") // The method will throw an exception when the response was not received in time.
.Build(); await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
await mqttClient.ConnectAsync(mqttClientOptions);
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{
// Access to a fully featured application message is not supported for RPC calls!
// The method will throw an exception when the response was not received in time.
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
Console.WriteLine("The RPC call was successful.");
} }
Console.WriteLine("The RPC call was successful.");
} }
/* /*
+15 -17
View File
@@ -25,28 +25,26 @@ public static class Server_Diagnostics_Samples
var mqttServerFactory = new MqttServerFactory(); var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{ {
// Attach the event handler. Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
// It is also possible to read additional data from the client response. This requires casting the response packet. // It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket; var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}"); Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket; var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}"); Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
} }
+12 -14
View File
@@ -25,23 +25,21 @@ public static class Server_Intercepting_Samples
var mqttServerFactory = new MqttServerFactory(); var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
mqttServer.InterceptingPublishAsync += args =>
{ {
mqttServer.InterceptingPublishAsync += args => // Here we only change the topic of the received application message.
{ // but also changing the payload etc. is required. Changing the QoS after
// Here we only change the topic of the received application message. // transmitting is not supported and makes no sense at all.
// but also changing the payload etc. is required. Changing the QoS after args.ApplicationMessage.Topic += "/manipulated";
// transmitting is not supported and makes no sense at all.
args.ApplicationMessage.Topic += "/manipulated";
return CompletedTask.Instance; return CompletedTask.Instance;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
} }
@@ -29,64 +29,62 @@ public static class Server_Retained_Messages_Samples
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default! // Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var server = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs =>
{ {
// Make sure that the server will load the retained messages. try
server.LoadingRetainedMessageAsync += async eventArgs =>
{ {
try var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
{ var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages; eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded."); Console.WriteLine("Retained messages loaded.");
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{
// Ignore because nothing is stored yet.
Console.WriteLine("No retained messages stored yet.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to persist the changed retained messages.
server.RetainedMessageChangedAsync += async eventArgs =>
{ {
try // Ignore because nothing is stored yet.
{ Console.WriteLine("No retained messages stored yet.");
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages. }
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be catch (Exception exception)
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to clear the retained messages when they are all deleted via API.
server.RetainedMessagesClearedAsync += _ =>
{ {
File.Delete(storePath); Console.WriteLine(exception);
return Task.CompletedTask; }
}; };
await server.StartAsync(); // Make sure to persist the changed retained messages.
server.RetainedMessageChangedAsync += async eventArgs =>
{
try
{
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
Console.WriteLine("Press Enter to exit."); var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
Console.ReadLine();
} var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to clear the retained messages when they are all deleted via API.
server.RetainedMessagesClearedAsync += _ =>
{
File.Delete(storePath);
return Task.CompletedTask;
};
await server.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
} }
sealed class MqttRetainedMessageModel sealed class MqttRetainedMessageModel
@@ -102,10 +100,7 @@ public static class Server_Retained_Messages_Samples
public static MqttRetainedMessageModel Create(MqttApplicationMessage message) public static MqttRetainedMessageModel Create(MqttApplicationMessage message)
{ {
if (message == null) ArgumentNullException.ThrowIfNull(message);
{
throw new ArgumentNullException(nameof(message));
}
return new MqttRetainedMessageModel return new MqttRetainedMessageModel
{ {
+50 -60
View File
@@ -22,17 +22,15 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information. * See _Run_Minimal_Server_ for more information.
*/ */
using (var mqttServer = await StartMqttServer()) using var mqttServer = await StartMqttServer();
{ // Let the client connect.
// Let the client connect. await Task.Delay(TimeSpan.FromSeconds(5));
await Task.Delay(TimeSpan.FromSeconds(5));
// Now disconnect the client (if connected). // Now disconnect the client (if connected).
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient"); var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
if (affectedClient != null) if (affectedClient != null)
{ {
await affectedClient.DisconnectAsync(); await affectedClient.DisconnectAsync();
}
} }
} }
@@ -44,18 +42,16 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information. * See _Run_Minimal_Server_ for more information.
*/ */
using (var mqttServer = await StartMqttServer()) using var mqttServer = await StartMqttServer();
{ // Create a new message using the builder as usual.
// Create a new message using the builder as usual. var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
// Now inject the new message at the broker. // Now inject the new message at the broker.
await mqttServer.InjectApplicationMessage( await mqttServer.InjectApplicationMessage(
new InjectedMqttApplicationMessage(message) new InjectedMqttApplicationMessage(message)
{ {
SenderClientId = "SenderClientId" SenderClientId = "SenderClientId"
}); });
}
} }
public static async Task Run_Minimal_Server() public static async Task Run_Minimal_Server()
@@ -77,16 +73,14 @@ public static class Server_Simple_Samples
// .WithDefaultEndpointPort(1234) // .WithDefaultEndpointPort(1234)
// .Build(); // .Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
public static async Task Run_Server_With_Logging() public static async Task Run_Server_With_Logging()
@@ -103,16 +97,14 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
public static async Task Validating_Connections() public static async Task Validating_Connections()
@@ -127,37 +119,35 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Setup connection validation before starting the server so that there is
// no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e =>
{ {
// Setup connection validation before starting the server so that there is if (e.ClientId != "ValidClientId")
// no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e =>
{ {
if (e.ClientId != "ValidClientId") e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
{ }
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
if (e.UserName != "ValidUser") if (e.UserName != "ValidUser")
{ {
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
if (e.Password != "SecretPassword") if (e.Password != "SecretPassword")
{ {
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
return Task.CompletedTask; return Task.CompletedTask;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
static async Task<MqttServer> StartMqttServer() static async Task<MqttServer> StartMqttServer()
+18 -24
View File
@@ -31,16 +31,14 @@ public static class Server_TLS_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build(); var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions)) using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
{ await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed! // Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync(); await mqttServer.StopAsync();
}
} }
static X509Certificate2 CreateSelfSignedCertificate(string oid) static X509Certificate2 CreateSelfSignedCertificate(string oid)
@@ -50,26 +48,22 @@ public static class Server_TLS_Samples
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost"); sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create()) using var rsa = RSA.Create();
{ var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add( certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false)); new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false)); certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build()); certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10))) using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
{ var pfxCertificate = new X509Certificate2(
var pfxCertificate = new X509Certificate2( certificate.Export(X509ContentType.Pfx),
certificate.Export(X509ContentType.Pfx), (string)null!,
(string)null!, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
return pfxCertificate; return pfxCertificate;
}
}
} }
} }
+11 -12
View File
@@ -4,20 +4,19 @@
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MQTTnet.AspTestApp.Pages namespace MQTTnet.AspTestApp.Pages;
public class IndexModel : PageModel
{ {
public class IndexModel : PageModel readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{ {
private readonly ILogger<IndexModel> _logger; _logger = logger;
}
public IndexModel(ILogger<IndexModel> logger) public void OnGet()
{ {
_logger = logger; _logger.LogDebug("OnGet");
}
public void OnGet()
{
}
} }
} }
@@ -33,10 +33,8 @@ public static class ApplicationBuilderExtensions
} }
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>(); var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false)) using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
{ await adapter.RunWebSocketConnectionAsync(webSocket, context);
await adapter.RunWebSocketConnectionAsync(webSocket, context);
}
}); });
return app; return app;
@@ -4,13 +4,12 @@
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore;
public static class ConnectionBuilderExtensions
{ {
public static class ConnectionBuilderExtensions public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder)
{ {
public static IConnectionBuilder UseMqtt(this IConnectionBuilder builder) return builder.UseConnectionHandler<MqttConnectionHandler>();
{
return builder.UseConnectionHandler<MqttConnectionHandler>();
}
} }
} }
@@ -6,22 +6,17 @@ using System;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore;
{
public static class EndpointRouterExtensions
{
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options => public static class EndpointRouterExtensions
{ {
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol; public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
}); {
} ArgumentNullException.ThrowIfNull(endpoints);
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
} }
} }
@@ -26,7 +26,7 @@
<PackageIcon>nuget.png</PackageIcon> <PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -38,13 +38,14 @@
<NuGetAuditLevel>low</NuGetAuditLevel> <NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditLevel>low</NuGetAuditLevel> <NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel> <AnalysisLevel>latest-Recommended</AnalysisLevel>
<IsAotCompatible>true</IsAotCompatible>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\Images\nuget.png"> <None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
<Pack>True</Pack> <None Include="..\..\LICENSE" Pack="true" PackagePath="" />
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -7,27 +7,26 @@ using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.AspNetCore namespace MQTTnet.AspNetCore;
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory
{ {
public sealed class MqttClientConnectionContextFactory : IMqttClientAdapterFactory public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
{ {
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger) if (options == null) throw new ArgumentNullException(nameof(options));
switch (options.ChannelOptions)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); case MqttClientTcpOptions tcpOptions:
switch (options.ChannelOptions)
{ {
case MqttClientTcpOptions tcpOptions: var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
{
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)); var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection); return new MqttConnectionContext(formatter, tcpConnection);
} }
default: default:
{ {
throw new NotSupportedException(); throw new NotSupportedException();
}
} }
} }
} }
@@ -61,21 +61,21 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
} }
} }
public string Endpoint public EndPoint RemoteEndPoint
{ {
get get
{ {
// mqtt over tcp // mqtt over tcp
if (_connection.RemoteEndPoint != null) if (_connection.RemoteEndPoint != null)
{ {
return _connection.RemoteEndPoint.ToString(); return _connection.RemoteEndPoint;
} }
// mqtt over websocket // mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>(); var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null) if (httpFeature?.RemoteIpAddress != null)
{ {
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString(); return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
} }
return null; return null;
@@ -33,13 +33,11 @@ public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapte
} }
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax)); var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
using (var adapter = new MqttConnectionContext(formatter, connection)) using var adapter = new MqttConnectionContext(formatter, connection);
var clientHandler = ClientHandler;
if (clientHandler != null)
{ {
var clientHandler = ClientHandler; await clientHandler(adapter).ConfigureAwait(false);
if (clientHandler != null)
{
await clientHandler(adapter).ConfigureAwait(false);
}
} }
} }
@@ -13,10 +13,7 @@ public static class MqttSubProtocolSelector
{ {
public static string SelectSubProtocol(HttpRequest request) public static string SelectSubProtocol(HttpRequest request)
{ {
if (request == null) ArgumentNullException.ThrowIfNull(request);
{
throw new ArgumentNullException(nameof(request));
}
string subProtocol = null; string subProtocol = null;
if (request.Headers.TryGetValue("Sec-WebSocket-Protocol", out var requestedSubProtocolValues)) 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) public static string SelectSubProtocol(IList<string> requestedSubProtocolValues)
{ {
if (requestedSubProtocolValues == null) ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
{
throw new ArgumentNullException(nameof(requestedSubProtocolValues));
}
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc. // 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")); 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. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -26,12 +27,10 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext) public async Task RunWebSocketConnectionAsync(WebSocket webSocket, HttpContext httpContext)
{ {
if (webSocket == null) ArgumentNullException.ThrowIfNull(webSocket);
{
throw new ArgumentNullException(nameof(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); var clientCertificate = await httpContext.Connection.GetClientCertificateAsync().ConfigureAwait(false);
try try
@@ -42,12 +41,10 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
if (clientHandler != null) if (clientHandler != null)
{ {
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)); 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)) using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
{ await clientHandler(channelAdapter).ConfigureAwait(false);
await clientHandler(channelAdapter).ConfigureAwait(false);
}
} }
} }
finally finally
@@ -22,10 +22,7 @@ public static class ReaderExtensions
out SequencePosition observed, out SequencePosition observed,
out int bytesRead) out int bytesRead)
{ {
if (formatter == null) ArgumentNullException.ThrowIfNull(formatter);
{
throw new ArgumentNullException(nameof(formatter));
}
packet = null; packet = null;
consumed = input.Start; consumed = input.Start;
@@ -16,15 +16,8 @@ public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, MqttServerOptions options)
{ {
if (services == null) ArgumentNullException.ThrowIfNull(services);
{ ArgumentNullException.ThrowIfNull(options);
throw new ArgumentNullException(nameof(services));
}
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
services.AddSingleton(options); services.AddSingleton(options);
services.AddHostedMqttServer(); services.AddHostedMqttServer();
@@ -34,10 +27,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure) public static IServiceCollection AddHostedMqttServer(this IServiceCollection services, Action<MqttServerOptionsBuilder> configure)
{ {
if (services == null) ArgumentNullException.ThrowIfNull(services);
{
throw new ArgumentNullException(nameof(services));
}
var serverOptionsBuilder = new MqttServerOptionsBuilder(); var serverOptionsBuilder = new MqttServerOptionsBuilder();
@@ -61,10 +51,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure) public static IServiceCollection AddHostedMqttServerWithServices(this IServiceCollection services, Action<AspNetMqttServerOptionsBuilder> configure)
{ {
if (services == null) ArgumentNullException.ThrowIfNull(services);
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton( services.AddSingleton(
s => s =>
@@ -89,20 +76,14 @@ public static class ServiceCollectionExtensions
public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger) public static void AddMqttLogger(this IServiceCollection services, IMqttNetLogger logger)
{ {
if (services == null) ArgumentNullException.ThrowIfNull(services);
{
throw new ArgumentNullException(nameof(services));
}
services.AddSingleton(logger); services.AddSingleton(logger);
} }
public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null) public static IServiceCollection AddMqttServer(this IServiceCollection serviceCollection, Action<MqttServerOptionsBuilder> configure = null)
{ {
if (serviceCollection is null) ArgumentNullException.ThrowIfNull(serviceCollection);
{
throw new ArgumentNullException(nameof(serviceCollection));
}
serviceCollection.AddMqttConnectionHandler(); serviceCollection.AddMqttConnectionHandler();
serviceCollection.AddHostedMqttServer(configure); serviceCollection.AddHostedMqttServer(configure);
+7 -13
View File
@@ -12,34 +12,28 @@ using System.Threading.Tasks;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
public class SocketAwaitable : ICriticalNotifyCompletion public class SocketAwaitable(PipeScheduler ioScheduler) : ICriticalNotifyCompletion
{ {
static readonly Action _callbackCompleted = () => static readonly Action CallbackCompleted = () =>
{ {
}; };
readonly PipeScheduler _ioScheduler;
int _bytesTransferred; int _bytesTransferred;
Action _callback; Action _callback;
SocketError _error; SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler) public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted);
{
_ioScheduler = ioScheduler;
}
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public void Complete(int bytesTransferred, SocketError socketError) public void Complete(int bytesTransferred, SocketError socketError)
{ {
_error = socketError; _error = socketError;
_bytesTransferred = bytesTransferred; _bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted); var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted);
if (continuation != null) if (continuation != null)
{ {
_ioScheduler.Schedule(state => ((Action)state)(), continuation); ioScheduler.Schedule(state => ((Action)state)(), continuation);
} }
} }
@@ -50,7 +44,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
public int GetResult() public int GetResult()
{ {
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted)); Debug.Assert(ReferenceEquals(_callback, CallbackCompleted));
_callback = null; _callback = null;
@@ -64,7 +58,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
if (ReferenceEquals(_callback, _callbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), _callbackCompleted)) if (ReferenceEquals(_callback, CallbackCompleted) || ReferenceEquals(Interlocked.CompareExchange(ref _callback, continuation, null), CallbackCompleted))
{ {
Task.Run(continuation); Task.Run(continuation);
} }
@@ -18,11 +18,11 @@ namespace MQTTnet.AspNetCore;
public sealed class SocketConnection : ConnectionContext public sealed class SocketConnection : ConnectionContext
{ {
readonly EndPoint _endPoint; readonly EndPoint _endPoint;
volatile bool _aborted; volatile bool _aborted;
IDuplexPipe _application; IDuplexPipe _application;
SocketReceiver _receiver; SocketReceiver _receiver;
SocketSender _sender; SocketSender _sender;
Socket _socket; Socket _socket;
public SocketConnection(EndPoint endPoint) public SocketConnection(EndPoint endPoint)
@@ -40,10 +40,13 @@ public sealed class SocketConnection : ConnectionContext
} }
public override string ConnectionId { get; set; } public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; } public override IFeatureCollection Features { get; }
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public override IDictionary<object, object> Items { get; set; } public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; } public override IDuplexPipe Transport { get; set; }
public override ValueTask DisposeAsync() public override ValueTask DisposeAsync()
@@ -78,7 +81,7 @@ public sealed class SocketConnection : ConnectionContext
IsConnected = true; IsConnected = true;
} }
Exception ConnectionAborted() static Exception ConnectionAborted()
{ {
return new MqttCommunicationException("Connection Aborted"); return new MqttCommunicationException("Connection Aborted");
} }
@@ -95,8 +98,7 @@ public sealed class SocketConnection : ConnectionContext
{ {
error = new MqttCommunicationException(ex); error = new MqttCommunicationException(ex);
} }
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted || catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument)
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
{ {
if (!_aborted) if (!_aborted)
{ {
@@ -123,10 +125,10 @@ public sealed class SocketConnection : ConnectionContext
{ {
if (_aborted) if (_aborted)
{ {
error = error ?? ConnectionAborted(); error ??= ConnectionAborted();
} }
_application.Output.Complete(error); await _application.Output.CompleteAsync(error);
} }
} }
@@ -0,0 +1,3 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
[assembly: DoNotParallelize]
+40 -43
View File
@@ -9,57 +9,54 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class AsyncLockBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [Benchmark]
[MemoryDiagnoser] public async Task Synchronize_100_Tasks()
public class AsyncLockBenchmark : BaseBenchmark
{ {
[Benchmark] const int tasksCount = 100;
public async Task Synchronize_100_Tasks()
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{ {
const int tasksCount = 100; tasks[i] = Task.Run(
async () =>
var tasks = new Task[tasksCount]; {
var asyncLock = new AsyncLock(); using (await asyncLock.EnterAsync().ConfigureAwait(false))
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{
tasks[i] = Task.Run(
async () =>
{ {
using (await asyncLock.EnterAsync().ConfigureAwait(false)) var localI = globalI;
{ await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
var localI = globalI; localI++;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data. globalI = localI;
localI++; }
globalI = localI; });
}
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (globalI != tasksCount)
{
throw new Exception($"Code is broken ({globalI})!");
}
} }
[Benchmark] await Task.WhenAll(tasks).ConfigureAwait(false);
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource()) if (globalI != tasksCount)
{
throw new Exception($"Code is broken ({globalI})!");
}
}
[Benchmark]
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using var cancellationToken = new CancellationTokenSource();
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{ {
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
} }
} }
} }
+3 -4
View File
@@ -2,9 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
public abstract class BaseBenchmark
{ {
public abstract class BaseBenchmark
{
}
} }
@@ -12,79 +12,78 @@ using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark : BaseBenchmark
{ {
[MemoryDiagnoser] MqttChannelAdapter _channelAdapter;
public sealed class ChannelAdapterBenchmark : BaseBenchmark int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
{ {
MqttChannelAdapter _channelAdapter; _stream.Position = 0;
int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
[Benchmark] for (var i = 0; i < 10000; i++)
public void Receive_10000_Messages()
{ {
_stream.Position = 0; _channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
for (var i = 0; i < 10000; i++)
{
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
} }
[Benchmark] _stream.Position = 0;
public void Send_10000_Messages() }
[Benchmark]
public void Send_10000_Messages()
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{ {
_stream.Position = 0; _channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
for (var i = 0; i < 10000; i++)
{
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
} }
[GlobalSetup] _stream.Position = 0;
public void Setup() }
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{ {
_packet = new MqttPublishPacket Topic = "A"
{ };
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join()); var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000; _iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length); _stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++) for (var i = 0; i < _iterations; i++)
{ {
_stream.Write(serializedPacket, 0, serializedPacket.Length); _stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
} }
static byte[] Join(params ArraySegment<byte>[] chunks) _stream.Position = 0;
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
return buffer.ToArray(); var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
} }
return buffer.ToArray();
} }
} }
+62 -62
View File
@@ -2,90 +2,90 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Diagnostics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttNetNullLogger _nullLogger;
[RPlotExporter] MqttNetSourceLogger _sourceNullLogger;
[MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger;
MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener;
bool _useHandler;
[GlobalSetup]
public void Setup()
{ {
MqttNetNullLogger _nullLogger; _nullLogger = new MqttNetNullLogger();
MqttNetSourceLogger _sourceNullLogger; _sourceNullLogger = _nullLogger.WithSource("Source");
MqttNetEventLogger _eventLogger; _eventLogger = new MqttNetEventLogger();
MqttNetSourceLogger _sourceEventLogger; _eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
MqttNetEventLogger _eventLoggerNoListener; _eventLoggerNoListener = new MqttNetEventLogger();
MqttNetSourceLogger _sourceEventLoggerNoListener; _sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
}
bool _useHandler; void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
{
[GlobalSetup] if (_useHandler)
public void Setup()
{ {
_nullLogger = new MqttNetNullLogger(); Debug.WriteLine(eventArgs.LogMessage.ToString());
_sourceNullLogger = _nullLogger.WithSource("Source");
_eventLogger = new MqttNetEventLogger();
_eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
_eventLoggerNoListener = new MqttNetEventLogger();
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
} }
}
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs) [Benchmark]
public void Log_10000_Messages_No_Listener()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{ {
if (_useHandler) _sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
{
eventArgs.LogMessage.ToString();
}
} }
}
[Benchmark] [Benchmark]
public void Log_10000_Messages_No_Listener() public void Log_10000_Messages_With_To_String()
{
_useHandler = true;
for (var i = 0; i < 10000; i++)
{ {
_useHandler = false; _sourceEventLogger.Verbose("test log message {0}", "parameter");
for (var i = 0; i < 10000; i++)
{
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
}
} }
}
[Benchmark] [Benchmark]
public void Log_10000_Messages_With_To_String() public void Log_10000_Messages_Without_To_String()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{ {
_useHandler = true; _sourceEventLogger.Verbose("test log message {0}", "parameter");
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
} }
}
[Benchmark] [Benchmark]
public void Log_10000_Messages_Without_To_String() public void Log_10000_Messages_With_NullLogger()
{
for (var i = 0; i < 10000; i++)
{ {
_useHandler = false; _sourceNullLogger.Verbose("test log message {0}", "parameter");
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_NullLogger()
{
for (var i = 0; i < 10000; i++)
{
_sourceNullLogger.Verbose("test log message {0}", "parameter");
}
} }
} }
} }
@@ -18,7 +18,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12"/> <PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -3,38 +3,38 @@ using BenchmarkDotNet.Jobs;
using System; using System;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] const int MaxLength = 1024 * 8;
[RPlotExporter, RankColumn]
[MemoryDiagnoser] byte[] _source;
public class MemoryCopyBenchmark byte[] _target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{ {
const int max_length = 1024 * 8; _source = new byte[MaxLength];
private byte[] source; _target = new byte[MaxLength];
private byte[] target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{
source = new byte[max_length];
target = new byte[max_length];
}
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(source, 0, target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
}
} }
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(_source, 0, _target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
}
} }
@@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
@@ -23,22 +22,19 @@ public class MessageDeliveryBenchmark : BaseBenchmark
int _messagesExpectedCount; int _messagesExpectedCount;
int _messagesReceivedCount; int _messagesReceivedCount;
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName; Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
MqttServer _mqttServer; MqttServer _mqttServer;
List<IMqttClient> _mqttSubscriberClients; List<IMqttClient> _mqttSubscriberClients;
[Params(1000, 10000)] public int _numPublishers;
[Params(5, 10, 20, 50)] public int _numSubscribedTopicsPerSubscriber;
[Params(10)] public int _numSubscribers;
[Params(1, 5)] public int _numTopicsPerPublisher;
Dictionary<string, string> _publisherByTopic; Dictionary<string, string> _publisherByTopic;
List<MqttApplicationMessage> _topicPublishMessages;
Dictionary<string, List<string>> _topicsByPublisher; Dictionary<string, List<string>> _topicsByPublisher;
[Params(1000, 10000)] public int NumPublishers;
[Params(5, 10, 20, 50)] public int NumSubscribedTopicsPerSubscriber;
[Params(10)] public int NumSubscribers;
[Params(1, 5)] public int NumTopicsPerPublisher;
[GlobalCleanup] [GlobalCleanup]
public void Cleanup() public void Cleanup()
{ {
@@ -64,14 +60,11 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer = null; _mqttServer = null;
} }
/// <summary>
/// Publish messages and wait for messages sent to subscribers
/// </summary>
[Benchmark] [Benchmark]
public void DeliverMessages() public void DeliverMessages()
{ {
// There should be one message received per publish for each subscribed topic // There should be one message received per publish for each subscribed topic
_messagesExpectedCount = NumSubscribedTopicsPerSubscriber * NumSubscribers; _messagesExpectedCount = _numSubscribedTopicsPerSubscriber * _numSubscribers;
// Loop for a while and exchange messages // Loop for a while and exchange messages
@@ -82,8 +75,6 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// same payload for all messages // same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 }; var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic // publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics) foreach (var topic in _allSubscribedTopics)
{ {
@@ -101,35 +92,23 @@ public class MessageDeliveryBenchmark : BaseBenchmark
} }
catch catch
{ {
// Ignore all errors.
} }
_cancellationTokenSource.Dispose(); _cancellationTokenSource.Dispose();
if (_messagesReceivedCount < _messagesExpectedCount) if (_messagesReceivedCount < _messagesExpectedCount)
{ {
throw new Exception(string.Format("Messages Received Count mismatch, expected {0}, received {1}", _messagesExpectedCount, _messagesReceivedCount)); throw new Exception($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}");
} }
} }
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
_lockMsgCount = new object(); _lockMsgCount = new object();
Dictionary<string, List<string>> singleWildcardTopicsByPublisher; TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out _topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var topics = _topicsByPublisher.First().Value;
_topicPublishMessages = new List<MqttApplicationMessage>();
// Prepare messages, same for each publisher
foreach (var topic in topics)
{
var message = new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }).Build();
_topicPublishMessages.Add(message);
}
// Create server // Create server
var serverFactory = new MqttServerFactory(); var serverFactory = new MqttServerFactory();
@@ -139,7 +118,7 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer.StartAsync().GetAwaiter().GetResult(); _mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients // Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>(); _mqttPublisherClientsByPublisherName = [];
foreach (var pt in _topicsByPublisher) foreach (var pt in _topicsByPublisher)
{ {
var publisherName = pt.Key; var publisherName = pt.Key;
@@ -151,13 +130,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Create subscriber clients // Create subscriber clients
_mqttSubscriberClients = new List<IMqttClient>(); _mqttSubscriberClients = new List<IMqttClient>();
for (var i = 0; i < NumSubscribers; i++) for (var i = 0; i < _numSubscribers; i++)
{ {
var mqttSubscriberClient = clientFactory.CreateMqttClient(); var mqttSubscriberClient = clientFactory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient); _mqttSubscriberClients.Add(mqttSubscriberClient);
var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build(); var subscriberOptions = new MqttClientOptionsBuilder().WithTcpServer("localhost").WithClientId("subscriber" + i).Build();
mqttSubscriberClient.ApplicationMessageReceivedAsync += r => mqttSubscriberClient.ApplicationMessageReceivedAsync += _ =>
{ {
// count messages and signal cancellation when expected message count is reached // count messages and signal cancellation when expected message count is reached
lock (_lockMsgCount) lock (_lockMsgCount)
@@ -171,12 +150,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
return Task.CompletedTask; return Task.CompletedTask;
}; };
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult(); mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
} }
var allTopics = new List<string>(); var allTopics = new List<string>();
_publisherByTopic = new Dictionary<string, string>(); _publisherByTopic = [];
foreach (var t in _topicsByPublisher) foreach (var t in _topicsByPublisher)
{ {
foreach (var topic in t.Value) foreach (var topic in t.Value)
@@ -189,21 +169,18 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Subscribe to NumSubscribedTopics topics spread across all topics // Subscribe to NumSubscribedTopics topics spread across all topics
_allSubscribedTopics = new List<string>(); _allSubscribedTopics = new List<string>();
var totalNumTopics = NumPublishers * NumTopicsPerPublisher; var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers); var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics) if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
{ {
throw new Exception( throw new Exception(
string.Format( $"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {totalNumTopics}, topic step: {topicIndexStep}");
"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics,
topicIndexStep));
} }
var topicIndex = 0; var topicIndex = 0;
foreach (var mqttSubscriber in _mqttSubscriberClients) foreach (var mqttSubscriber in _mqttSubscriberClients)
{ {
for (var i = 0; i < NumSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep) for (var i = 0; i < _numSubscribedTopicsPerSubscriber; ++i, topicIndex += topicIndexStep)
{ {
var topic = allTopics[topicIndex]; var topic = allTopics[topicIndex];
_allSubscribedTopics.Add(topic); _allSubscribedTopics.Add(topic);
@@ -9,65 +9,64 @@ using Microsoft.AspNetCore.Hosting;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] IWebHost _host;
[MemoryDiagnoser] IMqttClient _mqttClient;
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{ {
IWebHost _host; _host = WebHost.CreateDefaultBuilder()
IMqttClient _mqttClient; .UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
MqttApplicationMessage _message; .ConfigureServices(services => {
services
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
.AddMqttConnectionHandler();
})
.Configure(app => {
app.UseMqttServer(_ => {
[GlobalSetup] });
public void Setup() })
.Build();
var factory = new MqttClientFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttClient.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
_host.Dispose();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{ {
_host = WebHost.CreateDefaultBuilder() _mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
.ConfigureServices(services => {
services
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
.AddMqttConnectionHandler();
})
.Configure(app => {
app.UseMqttServer(s => {
});
})
.Build();
var factory = new MqttClientFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttClient.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
_host.Dispose();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
} }
} }
} }
@@ -8,36 +8,35 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttBufferReaderBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] byte[] _buffer;
[MemoryDiagnoser] int _bufferLength;
public class MqttBufferReaderBenchmark
[GlobalSetup]
public void GlobalSetup()
{ {
byte[] _buffer; var writer = new MqttBufferWriter(1024, 1024);
int _bufferLength; writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
[GlobalSetup] _buffer = writer.GetBuffer();
public void GlobalSetup() _bufferLength = writer.Length;
{ }
var writer = new MqttBufferWriter(1024, 1024);
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
_buffer = writer.GetBuffer(); [Benchmark]
_bufferLength = writer.Length; public void Use_Span()
} {
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark] [Benchmark]
public void Use_Span() public void Use_Encoding()
{ {
var span = _buffer.AsSpan(0, _bufferLength); Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
} }
} }
@@ -8,89 +8,88 @@ using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] readonly byte[] _demoPayload = new byte[1024];
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark byte[] _readPayload;
[GlobalCleanup]
public void GlobalCleanup()
{ {
readonly byte[] _demoPayload = new byte[1024]; }
byte[] _readPayload; [GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
[GlobalCleanup] var writer = new MqttBufferWriter(4096, 65535);
public void GlobalCleanup() writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
[Benchmark]
public void Read_100_000_Messages()
{
var reader = new MqttBufferReader();
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
for (var i = 0; i < 100000; i++)
{ {
reader.Seek(0);
reader.ReadString();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadVariableByteInteger();
reader.ReadString();
reader.ReadVariableByteInteger();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadString();
reader.ReadBinaryData();
} }
}
[GlobalSetup] [Benchmark]
public void GlobalSetup() public void Write_100_000_Messages()
{
var writer = new MqttBufferWriter(4096, 65535);
for (var i = 0; i < 100000; i++)
{ {
TestEnvironment.EnableLogger = false;
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01); writer.WriteByte(0x01);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382); writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string."); writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489); writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload); writer.WriteBinary(_demoPayload);
writer.WriteByte(2); writer.WriteByte(2);
writer.WriteByte(0x02); writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug"); writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload); writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray(); writer.Reset(0);
}
[Benchmark]
public void Read_100_000_Messages()
{
var reader = new MqttBufferReader();
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
for (var i = 0; i < 100000; i++)
{
reader.Seek(0);
reader.ReadString();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadVariableByteInteger();
reader.ReadString();
reader.ReadVariableByteInteger();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadString();
reader.ReadBinaryData();
}
}
[Benchmark]
public void Write_100_000_Messages()
{
var writer = new MqttBufferWriter(4096, 65535);
for (var i = 0; i < 100000; i++)
{
writer.WriteString("A relative short string.");
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
writer.Reset(0);
}
} }
} }
} }
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Buffers; using System.Buffers;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
+102 -105
View File
@@ -5,138 +5,135 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
public static class Program
{ {
public static class Program static int _selectedBenchmarkIndex;
static List<Type> _benchmarks;
public static void Main(string[] arguments)
{ {
static int _selectedBenchmarkIndex; _benchmarks = CollectBenchmarks();
static List<Type> _benchmarks; HandleArguments(arguments);
public static void Main(string[] arguments) while (true)
{ {
_benchmarks = CollectBenchmarks(); RenderMenu();
HandleArguments(arguments);
while (true) var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{ {
RenderMenu(); _selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{ {
_selectedBenchmarkIndex++; _selectedBenchmarkIndex = 0;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = 0;
}
}
else if (key.Key == ConsoleKey.UpArrow)
{
_selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
else if (key.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
else if (key.Key == ConsoleKey.Enter)
{
break;
} }
} }
else if (key.Key == ConsoleKey.UpArrow)
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
{ {
if (typeof(BaseBenchmark).IsAssignableFrom(type)) _selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{ {
if (type != typeof(BaseBenchmark)) _selectedBenchmarkIndex = _benchmarks.Count - 1;
{
benchmarks.Add(type);
}
} }
} }
else if (key.Key == ConsoleKey.Escape)
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{ {
return; Environment.Exit(0);
} }
else if (key.Key == ConsoleKey.Enter)
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{ {
_selectedBenchmarkIndex = benchmarkIndex; break;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
} }
} }
static void RenderMenu() BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
{ {
Console.Clear(); if (typeof(BaseBenchmark).IsAssignableFrom(type))
Console.WriteLine($"MQTTnet - Benchmarks");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
{ {
var benchmark = _benchmarks[index]; if (type != typeof(BaseBenchmark))
if (_selectedBenchmarkIndex == index)
{ {
Console.Write("-> "); benchmarks.Add(type);
} }
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
} }
}
Console.WriteLine(); return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{
return;
}
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{
_selectedBenchmarkIndex = benchmarkIndex;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
} }
} }
static void RenderMenu()
{
Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
{
var benchmark = _benchmarks[index];
if (_selectedBenchmarkIndex == index)
{
Console.Write("-> ");
}
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
}
Console.WriteLine();
}
} }
@@ -1,240 +1,86 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
[GlobalSetup] [SimpleJob(RuntimeMoniker.Net60)]
public void GlobalSetup() [RPlotExporter, RankColumn]
{ [MemoryDiagnoser]
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); public class ReaderExtensionsBenchmark
var mqttMessage = new MqttApplicationMessageBuilder() {
MqttPacketFormatterAdapter _mqttPacketFormatter;
MemoryStream _stream;
[GlobalSetup]
public void GlobalSetup()
{
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic") .WithTopic("topic")
.WithPayload(new byte[10 * 1024]) .WithPayload(new byte[10 * 1024])
.Build(); .Build();
var packet = MqttPublishPacketFactory.Create(mqttMessage); var packet = MqttPublishPacketFactory.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet); var buffer = _mqttPacketFormatter.Encode(packet);
stream = new MemoryStream(); _stream = new MemoryStream();
stream.Write(buffer.Packet); _stream.Write(buffer.Packet);
stream.Write(buffer.Payload.ToArray()); _stream.Write(buffer.Payload.ToArray());
mqttPacketFormatter.Cleanup(); _mqttPacketFormatter.Cleanup();
} }
[Benchmark(Baseline = true)] [Benchmark]
public async Task Before() public async Task After()
{
_stream.Position = 0;
var input = PipeReader.Create(_stream);
while (true)
{ {
stream.Position = 0; ReadResult readResult;
var input = PipeReader.Create(stream); var readTask = input.ReadAsync();
if (readTask.IsCompleted)
while (true)
{ {
ReadResult readResult; readResult = readTask.Result;
var readTask = input.ReadAsync(); }
if (readTask.IsCompleted) else
{ {
readResult = readTask.Result; readResult = await readTask.ConfigureAwait(false);
} }
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer; var buffer = readResult.Buffer;
var consumed = buffer.Start; var consumed = buffer.Start;
var observed = buffer.Start; var observed = buffer.Start;
try try
{
if (!buffer.IsEmpty)
{ {
if (!buffer.IsEmpty) if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
{ {
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received)) break;
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
} }
} }
finally else if (readResult.IsCompleted)
{ {
// The buffer was sliced up to where it was consumed, so we can just advance to the start. throw new MqttCommunicationException("Connection Aborted");
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
} }
} }
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
} }
[Benchmark]
public async Task After()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
public static class ReaderExtensions_Before
{
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
packet = null;
consumed = input.Start;
observed = input.End;
bytesRead = 0;
var copy = input;
if (copy.Length < 2)
{
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
}
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = GetMemory(bodySlice).ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
{
return input.First;
}
// Should be rare
return input.ToArray();
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
do
{
if (index == temp.Length)
{
return false;
}
encodedByte = temp[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
}
} }
} }
@@ -3,28 +3,27 @@ using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server; using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [GlobalSetup]
[RPlotExporter, RankColumn] public void GlobalSetup()
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{ {
[GlobalSetup] TestEnvironment.EnableLogger = false;
public void GlobalSetup() }
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup] [GlobalCleanup]
public void GlobalCleanup() public void GlobalCleanup()
{ {
} }
[Benchmark] [Benchmark]
public void Handle_100_000_Messages_In_Receiving_Client() public void Handle_100_000_Messages_In_Receiving_Client()
{ {
new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult(); //new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
}
} }
} }
@@ -7,63 +7,61 @@ using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] MemoryStream _stream;
[RPlotExporter, RankColumn] MqttPacketBuffer _buffer;
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark [GlobalSetup]
public void GlobalSetup()
{ {
MemoryStream stream; _stream = new MemoryStream(1024);
MqttPacketBuffer buffer; var packet = new ArraySegment<byte>(new byte[10]);
_buffer = new MqttPacketBuffer(packet);
}
[GlobalSetup] [Benchmark(Baseline = true)]
public void GlobalSetup() public async ValueTask Before()
{
_stream.Position = 0;
var output = PipeWriter.Create(_stream);
WritePacketBuffer(output, _buffer);
await output.FlushAsync();
}
[Benchmark]
public async ValueTask After()
{
_stream.Position = 0;
var output = PipeWriter.Create(_stream);
if (_buffer.Payload.Length == 0)
{ {
stream = new MemoryStream(1024); await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
var packet = new ArraySegment<byte>(new byte[10]);
buffer = new MqttPacketBuffer(packet);
} }
else
[Benchmark(Baseline = true)]
public async ValueTask Before()
{ {
stream.Position = 0; WritePacketBuffer(output, _buffer);
var output = PipeWriter.Create(stream); await output.FlushAsync().ConfigureAwait(false);
WritePacketBuffer(output, buffer);
await output.FlushAsync();
}
[Benchmark]
public async ValueTask After()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
if (buffer.Payload.Length == 0)
{
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
}
else
{
WritePacketBuffer(output, buffer);
await output.FlushAsync().ConfigureAwait(false);
}
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length);
} }
} }
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
output.Advance(buffer.Length);
}
} }
@@ -15,104 +15,104 @@ using MQTTnet.Formatter.V3;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using System.Buffers; using System.Buffers;
using System.Net;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttPacket _packet;
[RPlotExporter] ArraySegment<byte> _serializedPacket;
[MemoryDiagnoser] IMqttPacketFormatter _serializer;
public class SerializerBenchmark : BaseBenchmark MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{ {
MqttPacket _packet; _packet = new MqttPublishPacket
ArraySegment<byte> _serializedPacket;
IMqttPacketFormatter _serializer;
MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{ {
_packet = new MqttPublishPacket Topic = "A"
{ };
Topic = "A"
};
_bufferWriter = new MqttBufferWriter(4096, 65535); _bufferWriter = new MqttBufferWriter(4096, 65535);
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311); _serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
_serializedPacket = _serializer.Encode(_packet).Join(); _serializedPacket = _serializer.Encode(_packet).Join();
}
[Benchmark]
public void Serialize_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_serializer.Encode(_packet);
_bufferWriter.Cleanup();
}
}
[Benchmark]
public void Deserialize_10000_Messages()
{
var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
channel.Reset();
reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
class BenchmarkMqttChannel : IMqttChannel
{
readonly ArraySegment<byte> _buffer;
int _position;
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
{
_buffer = buffer;
_position = _buffer.Offset;
} }
[Benchmark] public EndPoint RemoteEndPoint { get; set; }
public void Serialize_10000_Messages()
public bool IsSecureConnection { get; } = false;
public X509Certificate2 ClientCertificate { get; set; }
public void Reset()
{ {
for (var i = 0; i < 10000; i++) _position = _buffer.Offset;
{
_serializer.Encode(_packet);
_bufferWriter.Cleanup();
}
} }
[Benchmark] public Task ConnectAsync(CancellationToken cancellationToken)
public void Deserialize_10000_Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); throw new NotSupportedException();
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
channel.Reset();
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
} }
class BenchmarkMqttChannel : IMqttChannel public Task DisconnectAsync(CancellationToken cancellationToken)
{ {
readonly ArraySegment<byte> _buffer; throw new NotSupportedException();
int _position; }
public BenchmarkMqttChannel(ArraySegment<byte> buffer) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
_buffer = buffer; Array.Copy(_buffer!.Array!, _position, buffer, offset, count);
_position = _buffer.Offset; _position += count;
}
public string Endpoint { get; } = string.Empty; return Task.FromResult(count);
}
public bool IsSecureConnection { get; } = false; public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public X509Certificate2 ClientCertificate { get; } public void Dispose()
{
public void Reset()
{
_position = _buffer.Offset;
}
public Task ConnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Array.Copy(_buffer.Array, _position, buffer, offset, count);
_position += count;
return Task.FromResult(count);
}
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public void Dispose()
{
}
} }
} }
} }
@@ -7,34 +7,33 @@ using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server; using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] [GlobalSetup]
[RPlotExporter, RankColumn] public void GlobalSetup()
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{ {
[GlobalSetup] TestEnvironment.EnableLogger = false;
public void GlobalSetup() }
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup] [GlobalCleanup]
public void GlobalCleanup() public void GlobalCleanup()
{ {
} }
[Benchmark] [Benchmark]
public void Handle_100_000_Messages_In_Server_MqttClient() public void Handle_100_000_Messages_In_Server_MqttClient()
{ {
new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult(); //new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
} }
//[Benchmark] [Benchmark]
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient() public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
{ {
new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult(); //new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
}
} }
} }
@@ -30,7 +30,7 @@ public class SubscribeBenchmark : BaseBenchmark
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
_topics = topicsByPublisher.Values.First(); _topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
+55 -56
View File
@@ -11,70 +11,69 @@ using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.AspNetCore; using MQTTnet.AspNetCore;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class TcpPipesBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] IDuplexPipe _client;
[MemoryDiagnoser] IDuplexPipe _server;
public class TcpPipesBenchmark : BaseBenchmark
[GlobalSetup]
public void Setup()
{ {
IDuplexPipe _client; var server = new TcpListener(IPAddress.Any, 1883);
IDuplexPipe _server;
[GlobalSetup] server.Start(1);
public void Setup()
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
}
[Benchmark]
public async Task Send_10000_Chunks_Pipe()
{
var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
var input = _client.Input;
while (read < expected)
{ {
var server = new TcpListener(IPAddress.Any, 1883); var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
server.Start(1); read += readresult.Buffer.Length;
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
} }
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
[Benchmark] var output = _server.Output;
public async Task Send_10000_Chunks_Pipe()
for (var i = 0; i < iterations; i++)
{ {
var size = 5; await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
var input = _client.Input;
while (read < expected)
{
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
read += readresult.Buffer.Length;
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
var output = _server.Output;
for (var i = 0; i < iterations; i++)
{
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
}
} }
} }
} }
@@ -2,276 +2,32 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)] readonly string _longTopic =
[RPlotExporter] "AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark [Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{ {
static readonly char[] TopicLevelSeparator = { '/' }; for (var i = 0; i < 100000; i++)
readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{ {
for (var i = 0; i < 100000; i++) MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
{ MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#"); MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking"); MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#"); MqttTopicFilterComparer.Compare("/finance", "+/+");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+"); MqttTopicFilterComparer.Compare("/finance", "/+");
MqttTopicFilterComparer.Compare("/finance", "+/+"); MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare("/finance", "/+"); MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer()
{
for (var i = 0; i < 100000; i++)
{
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+");
MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_StringSplitMethod()
{
for (var i = 0; i < 100000; i++)
{
LegacyMethodByStringSplit("sport/tennis/player1", "sport/#");
LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking");
LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+");
LegacyMethodByStringSplit("/finance", "+/+");
LegacyMethodByStringSplit("/finance", "/+");
LegacyMethodByStringSplit("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[GlobalSetup]
public void Setup()
{
}
static bool LegacyMethodByStringSplit(string topic, string filter)
{
if (topic == null)
{
throw new ArgumentNullException(nameof(topic));
}
if (filter == null)
{
throw new ArgumentNullException(nameof(filter));
}
if (string.Equals(topic, filter, StringComparison.Ordinal))
{
return true;
}
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
// # > In either case it MUST be the last character specified in the Topic Filter [MQTT-4.7.1-2].
for (var i = 0; i < fragmentsFilter.Length; i++)
{
if (fragmentsFilter[i] == "+")
{
continue;
}
if (fragmentsFilter[i] == "#")
{
return true;
}
if (i >= fragmentsTopic.Length)
{
return false;
}
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
{
return false;
}
}
return fragmentsTopic.Length == fragmentsFilter.Length;
}
public static class MqttTopicFilterComparerWithoutPointer
{
public const char LevelSeparator = '/';
public const char MultiLevelWildcard = '#';
public const char SingleLevelWildcard = '+';
public const char ReservedTopicPrefix = '$';
public static MqttTopicFilterCompareResult Compare(string topic, string filter)
{
if (string.IsNullOrEmpty(topic))
{
return MqttTopicFilterCompareResult.TopicInvalid;
}
if (string.IsNullOrEmpty(filter))
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
var filterOffset = 0;
var filterLength = filter.Length;
var topicOffset = 0;
var topicLength = topic.Length;
var topicPointer = topic;
var filterPointer = filter;
var isMultiLevelFilter = filterPointer[filterLength - 1] == MultiLevelWildcard;
var isReservedTopic = topicPointer[0] == ReservedTopicPrefix;
if (isReservedTopic && filterLength == 1 && isMultiLevelFilter)
{
// It is not allowed to receive i.e. '$foo/bar' with filter '#'.
return MqttTopicFilterCompareResult.NoMatch;
}
if (isReservedTopic && filterPointer[0] == SingleLevelWildcard)
{
// It is not allowed to receive i.e. '$SYS/monitor/Clients' with filter '+/monitor/Clients'.
return MqttTopicFilterCompareResult.NoMatch;
}
if (filterLength == 1 && isMultiLevelFilter)
{
// Filter '#' matches basically everything.
return MqttTopicFilterCompareResult.IsMatch;
}
// Go through the filter char by char.
while (filterOffset < filterLength && topicOffset < topicLength)
{
// Check if the current char is a multi level wildcard. The char is only allowed
// at the very las position.
if (filterPointer[filterOffset] == MultiLevelWildcard && filterOffset != filterLength - 1)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
if (filterPointer[filterOffset] == topicPointer[topicOffset])
{
if (topicOffset == topicLength - 1)
{
// Check for e.g. "foo" matching "foo/#"
if (filterOffset == filterLength - 3 && filterPointer[filterOffset + 1] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
// Check for e.g. "foo/" matching "foo/#"
if (filterOffset == filterLength - 2 && filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
}
filterOffset++;
topicOffset++;
// Check if the end was reached and i.e. "foo/bar" matches "foo/bar"
if (filterOffset == filterLength && topicOffset == topicLength)
{
return MqttTopicFilterCompareResult.IsMatch;
}
var endOfTopic = topicOffset == topicLength;
if (endOfTopic && filterOffset == filterLength - 1 && filterPointer[filterOffset] == SingleLevelWildcard)
{
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
return MqttTopicFilterCompareResult.IsMatch;
}
}
else
{
if (filterPointer[filterOffset] == SingleLevelWildcard)
{
// Check for invalid "+foo" or "a/+foo" subscription
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
// Check for bad "foo+" or "foo+/a" subscription
if (filterOffset < filterLength - 1 && filterPointer[filterOffset + 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
filterOffset++;
while (topicOffset < topicLength && topicPointer[topicOffset] != LevelSeparator)
{
topicOffset++;
}
if (topicOffset == topicLength && filterOffset == filterLength)
{
return MqttTopicFilterCompareResult.IsMatch;
}
}
else if (filterPointer[filterOffset] == MultiLevelWildcard)
{
if (filterOffset > 0 && filterPointer[filterOffset - 1] != LevelSeparator)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
if (filterOffset + 1 != filterLength)
{
return MqttTopicFilterCompareResult.FilterInvalid;
}
return MqttTopicFilterCompareResult.IsMatch;
}
else
{
// Check for e.g. "foo/bar" matching "foo/+/#".
if (filterOffset > 0 && filterOffset + 2 == filterLength && topicOffset == topicLength && filterPointer[filterOffset - 1] == SingleLevelWildcard &&
filterPointer[filterOffset] == LevelSeparator && isMultiLevelFilter)
{
return MqttTopicFilterCompareResult.IsMatch;
}
return MqttTopicFilterCompareResult.NoMatch;
}
}
}
return MqttTopicFilterCompareResult.NoMatch;
}
} }
} }
} }
+62 -63
View File
@@ -1,84 +1,83 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
public class TopicGenerator
{ {
public class TopicGenerator public static void Generate(
int numPublishers,
int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
{ {
public static void Generate( topicsByPublisher = new Dictionary<string, List<string>>();
int numPublishers, singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
int numTopicsPerPublisher, multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher, // Find some reasonable distribution across three topic levels
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher) var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{ {
topicsByPublisher = new Dictionary<string, List<string>>(); topicsPerLevel = 1;
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>(); }
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels var numLevel1Topics = topicsPerLevel;
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0); var numLevel2Topics = topicsPerLevel;
if (topicsPerLevel <= 0)
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
{
maxNumLevel3Topics = 1;
}
for (var p = 0; p < numPublishers; ++p)
{
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{ {
topicsPerLevel = 1; for (var l2 = 0; l2 < numLevel2Topics; ++l2)
}
var numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
{
maxNumLevel3Topics = 1;
}
for (var p = 0; p < numPublishers; ++p)
{
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{ {
for (var l2 = 0; l2 < numLevel2Topics; ++l2) for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{ {
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3) if (publisherTopicCount >= numTopicsPerPublisher)
{ {
if (publisherTopicCount >= numTopicsPerPublisher) break;
{
break;
}
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
++publisherTopicCount;
} }
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
++publisherTopicCount;
} }
} }
} }
} }
}
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher) static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
{
List<string> topicList;
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
{ {
List<string> topicList; topicList = new List<string>();
if (!topicsByPublisher.TryGetValue(publisherName, out topicList)) topicsByPublisher.Add(publisherName, topicList);
{
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
} }
topicList.Add(topic);
} }
} }
@@ -14,15 +14,15 @@ public class UnsubscribeBenchmark : BaseBenchmark
{ {
const int NumPublishers = 1; const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000; const int NumTopicsPerPublisher = 10000;
IMqttClient _mqttClient; IMqttClient _mqttClient;
MqttServer _mqttServer; MqttServer _mqttServer;
List<string> _topics; List<string> _topics;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out var singleWildcardTopicsByPublisher, out var multiWildcardTopicsByPublisher); TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out var topicsByPublisher, out _, out _);
_topics = topicsByPublisher.Values.First(); _topics = topicsByPublisher.Values.First();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build(); var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
@@ -4,30 +4,26 @@
using System; using System;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{ {
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{ {
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context) ArgumentNullException.ThrowIfNull(context);
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{ {
if (context == null) throw new ArgumentException("The method name cannot contain /, + or #.");
{
throw new ArgumentNullException(nameof(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";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
} }
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
} }
} }
@@ -8,12 +8,16 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
{
public interface IMqttRpcClient : IDisposable
{
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default); public interface IMqttRpcClient : IDisposable
} {
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object>? parameters = null);
Task<byte[]> ExecuteAsync(
string methodName,
byte[] payload,
MqttQualityOfServiceLevel qualityOfServiceLevel,
IDictionary<string, object>? parameters = null,
CancellationToken cancellationToken = default);
} }
@@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public interface IMqttRpcClientTopicGenerationStrategy
{ {
public interface IMqttRpcClientTopicGenerationStrategy MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context);
{
MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context);
}
} }
@@ -26,7 +26,7 @@
<PackageIcon>nuget.png</PackageIcon> <PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -36,13 +36,14 @@
<NuGetAudit>true</NuGetAudit> <NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel> <NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel> <AnalysisLevel>latest-Recommended</AnalysisLevel>
<IsAotCompatible>true</IsAotCompatible>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\Images\nuget.png"> <None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
<Pack>True</Pack> <None Include="..\..\LICENSE" Pack="true" PackagePath="" />
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -4,33 +4,25 @@
using System; using System;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public static class MqttFactoryExtensions
{ {
public static class MqttFactoryExtensions public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
{ {
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient) return clientFactory.CreateMqttRpcClient(
{ mqttClient,
return clientFactory.CreateMqttRpcClient( new MqttRpcClientOptions
mqttClient,
new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{
if (mqttClient == null)
{ {
throw new ArgumentNullException(nameof(mqttClient)); TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
} });
}
if (rpcClientOptions == null) public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{ {
throw new ArgumentNullException(nameof(rpcClientOptions)); ArgumentNullException.ThrowIfNull(mqttClient);
} ArgumentNullException.ThrowIfNull(rpcClientOptions);
return new MqttRpcClient(mqttClient, rpcClientOptions); return new MqttRpcClient(mqttClient, rpcClientOptions);
}
} }
} }
+115 -113
View File
@@ -6,7 +6,6 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -14,131 +13,134 @@ using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public sealed class MqttRpcClient : IMqttRpcClient
{ {
public sealed class MqttRpcClient : IMqttRpcClient readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{ {
readonly IMqttClient _mqttClient; _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
readonly MqttRpcClientOptions _options; _options = options ?? throw new ArgumentNullException(nameof(options));
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>(); _mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options) public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{ {
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient)); tcs.Value.TrySetCanceled();
_options = options ?? throw new ArgumentNullException(nameof(options));
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
} }
public void Dispose() _waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(
TimeSpan timeout,
string methodName,
byte[] payload,
MqttQualityOfServiceLevel qualityOfServiceLevel,
IDictionary<string, object>? parameters = null)
{
using var timeoutToken = new CancellationTokenSource(timeout);
try
{ {
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync; return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
} }
catch (OperationCanceledException exception)
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{ {
using (var timeoutToken = new CancellationTokenSource(timeout)) if (timeoutToken.IsCancellationRequested)
{ {
try throw new MqttCommunicationTimedOutException(exception);
{
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
if (timeoutToken.IsCancellationRequested)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
}
}
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
if (methodName == null)
{
throw new ArgumentNullException(nameof(methodName));
} }
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); throw;
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
} }
} }
public async Task<byte[]> ExecuteAsync(
string methodName,
byte[] payload,
MqttQualityOfServiceLevel qualityOfServiceLevel,
IDictionary<string, object>? parameters = null,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(methodName);
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options!.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
} }
@@ -8,17 +8,16 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public static class MqttRpcClientExtensions
{ {
public static class MqttRpcClientExtensions public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object>? parameters = null)
{ {
public static Task<byte[]> ExecuteAsync(this IMqttRpcClient client, TimeSpan timeout, string methodName, string payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null) if (client == null) throw new ArgumentNullException(nameof(client));
{
if (client == null) throw new ArgumentNullException(nameof(client));
var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty); var buffer = Encoding.UTF8.GetBytes(payload ?? string.Empty);
return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters); return client.ExecuteAsync(timeout, methodName, buffer, qualityOfServiceLevel, parameters);
}
} }
} }
@@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public sealed class MqttRpcClientOptions
{ {
public sealed class MqttRpcClientOptions public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
{
public IMqttRpcClientTopicGenerationStrategy TopicGenerationStrategy { get; set; } = new DefaultMqttRpcClientTopicGenerationStrategy();
}
} }
@@ -4,25 +4,24 @@
using System; using System;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public sealed class MqttRpcClientOptionsBuilder
{ {
public sealed class MqttRpcClientOptionsBuilder IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptions Build()
{ {
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy(); return new MqttRpcClientOptions
public MqttRpcClientOptions Build()
{ {
return new MqttRpcClientOptions TopicGenerationStrategy = _topicGenerationStrategy
{ };
TopicGenerationStrategy = _topicGenerationStrategy }
};
}
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value) public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{ {
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value)); _topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this; return this;
}
} }
} }
@@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
{
public sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
public string ResponseTopic { get; set; } public sealed class MqttRpcTopicPair
} {
public required string RequestTopic { get; init; }
public required string ResponseTopic { get; init; }
} }
@@ -6,37 +6,40 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public sealed class TopicGenerationContext
{ {
public sealed class TopicGenerationContext public TopicGenerationContext(
IMqttClient mqttClient,
MqttRpcClientOptions options,
string methodName,
IDictionary<string, object>? parameters,
MqttQualityOfServiceLevel qualityOfServiceLevel)
{ {
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel) Parameters = parameters;
{ QualityOfServiceLevel = qualityOfServiceLevel;
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName)); MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Parameters = parameters; Options = options ?? throw new ArgumentNullException(nameof(options));
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
} }
public string MethodName { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
public IDictionary<string, object>? Parameters { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
} }
@@ -28,10 +28,13 @@
<PackageIcon>nuget.png</PackageIcon> <PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes> <PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591;NETSDK1138</NoWarn>
<IsAotCompatible>true</IsAotCompatible>
<LangVersion>default</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -39,15 +42,16 @@
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" /> <None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <None Include="..\..\LICENSE" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -41,10 +41,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
/// </exception> /// </exception>
public MqttTopicTemplate(string topicTemplate) public MqttTopicTemplate(string topicTemplate)
{ {
if (topicTemplate == null) ArgumentNullException.ThrowIfNull(topicTemplate);
{
throw new ArgumentNullException(nameof(topicTemplate));
}
MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate); MqttTopicValidator.ThrowIfInvalidSubscribe(topicTemplate);
@@ -233,7 +230,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
throw new ArgumentException("the topic has to match this template", nameof(topic)); throw new ArgumentException("the topic has to match this template", nameof(topic));
} }
return parseParameterValuesInternal(topic); return ParseParameterValuesInternal(topic);
} }
/// <summary> /// <summary>
@@ -333,7 +330,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value)); return parameters.Aggregate(this, (t, p) => t.TrySetParameter(p.parameter, p.value));
} }
IEnumerable<(string parameter, int index, string value)> parseParameterValuesInternal(string topic) IEnumerable<(string parameter, int index, string value)> ParseParameterValuesInternal(string topic)
{ {
// because we have a match, we know the segment array is at least the template's length // because we have a match, we know the segment array is at least the template's length
var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator); var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator);
@@ -5,28 +5,27 @@
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class MqttServerClientDisconnectOptions
{ {
public sealed class MqttServerClientDisconnectOptions public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
{
public MqttDisconnectReasonCode ReasonCode { get; set; } = MqttDisconnectReasonCode.NormalDisconnection;
/// <summary> /// <summary>
/// The reason string is sent to every client via a DISCONNECT packet. /// The reason string is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; set; }
/// <summary> /// <summary>
/// The server reference is sent to every client via a DISCONNECT packet. /// The server reference is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public string ServerReference { get; set; } public string ServerReference { get; set; }
/// <summary> /// <summary>
/// These user properties are sent to every client via a DISCONNECT packet. /// These user properties are sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; set; }
}
} }
@@ -5,50 +5,49 @@
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class MqttServerClientDisconnectOptionsBuilder
{ {
public sealed class MqttServerClientDisconnectOptionsBuilder readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
public MqttServerClientDisconnectOptions Build()
{ {
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions(); return _options;
}
public MqttServerClientDisconnectOptions Build() public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
{
_options.ReasonCode = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
{
_options.ReasonString = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
{
_options.ServerReference = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
{
_options.UserProperties = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{ {
return _options; _options.UserProperties = new List<MqttUserProperty>();
} }
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value) _options.UserProperties.Add(new MqttUserProperty(name, value));
{ return this;
_options.ReasonCode = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
{
_options.ReasonString = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
{
_options.ServerReference = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
{
_options.UserProperties = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
} }
} }
@@ -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; init; }
public List<MqttUserProperty>? UserProperties { get; init; }
public byte[]? AuthenticationData { get; init; }
}
@@ -0,0 +1,34 @@
using MQTTnet.Packets;
namespace MQTTnet.Server.EnhancedAuthentication;
public static class ExchangeEnhancedAuthenticationResultFactory
{
public static ExchangeEnhancedAuthenticationResult Create(MqttAuthPacket authPacket)
{
ArgumentNullException.ThrowIfNull(authPacket);
return new ExchangeEnhancedAuthenticationResult
{
AuthenticationData = authPacket.AuthenticationData,
ReasonString = authPacket.ReasonString,
UserProperties = authPacket.UserProperties
};
}
public static ExchangeEnhancedAuthenticationResult Create(MqttDisconnectPacket disconnectPacket)
{
ArgumentNullException.ThrowIfNull(disconnectPacket);
return new ExchangeEnhancedAuthenticationResult
{
AuthenticationData = null,
ReasonString = disconnectPacket.ReasonString,
UserProperties = disconnectPacket.UserProperties
// SessionExpiryInterval makes no sense because the connection is not yet made!
// ServerReferences makes no sense when the client initiated a DISCONNECT!
};
}
}
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
namespace MQTTnet.Server public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
{ {
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
{ {
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped) SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
{ ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId)); IsDropped = isDropped;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
IsDropped = isDropped;
}
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
} }
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
} }
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
namespace MQTTnet.Server public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{ {
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{ {
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId) ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
{ SenderId = senderId;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
} }
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
} }
@@ -2,45 +2,49 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
{ {
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs public ClientAcknowledgedPublishPacketEventArgs(string clientId, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
{ {
public ClientAcknowledgedPublishPacketEventArgs(string clientId, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket)); AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
}
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
} }
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </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>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
} }
@@ -2,60 +2,67 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Net;
using System.Text;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientConnectedEventArgs : EventArgs
{ {
public sealed class ClientConnectedEventArgs : EventArgs readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
{ {
readonly MqttConnectPacket _connectPacket; _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems) RemoteEndPoint = remoteEndPoint;
{ SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// 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;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public string Endpoint { get; }
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <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 the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
} }
public byte[]? AuthenticationData => _connectPacket.AuthenticationData;
public string? AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// 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!;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
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.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <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 the user name of the connected client.
/// </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>
/// </summary>
public List<MqttUserProperty>? UserProperties => _connectPacket?.UserProperties;
} }
@@ -2,75 +2,88 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Net;
using System.Text;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientDisconnectedEventArgs : EventArgs
{ {
public sealed class ClientDisconnectedEventArgs : EventArgs readonly MqttConnectPacket _connectPacket;
readonly MqttDisconnectPacket _disconnectPacket;
public ClientDisconnectedEventArgs(
MqttConnectPacket connectPacket,
MqttDisconnectPacket disconnectPacket,
MqttClientDisconnectType disconnectType,
EndPoint remoteEndPoint,
IDictionary sessionItems)
{ {
readonly MqttDisconnectPacket _disconnectPacket; DisconnectType = disconnectType;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
public ClientDisconnectedEventArgs( _connectPacket = connectPacket;
string clientId, // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
MqttDisconnectPacket disconnectPacket, _disconnectPacket = disconnectPacket;
MqttClientDisconnectType disconnectType,
string endpoint,
IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
DisconnectType = disconnectType;
Endpoint = endpoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
_disconnectPacket = disconnectPacket;
}
/// <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 { get; }
public MqttClientDisconnectType DisconnectType { get; }
public string Endpoint { get; }
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <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 the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
} }
/// <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!;
/// <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 EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string? Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string? ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <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 the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty>? UserProperties => _disconnectPacket?.UserProperties;
} }
@@ -2,36 +2,40 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientSubscribedTopicEventArgs : EventArgs
{ {
public sealed class ClientSubscribedTopicEventArgs : EventArgs public ClientSubscribedTopicEventArgs(string clientId, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems)
{ {
public ClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter, IDictionary sessionItems) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <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 { get; }
/// <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 the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
} }
/// <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 { 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>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
} }
@@ -2,35 +2,39 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs
{ {
public sealed class ClientUnsubscribedTopicEventArgs : EventArgs public ClientUnsubscribedTopicEventArgs(string clientId, string userName, string topicFilter, IDictionary sessionItems)
{ {
public ClientUnsubscribedTopicEventArgs(string clientId, string topicFilter, IDictionary sessionItems) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <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 { get; }
/// <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 filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public string TopicFilter { get; }
} }
/// <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 { 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>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public string TopicFilter { get; }
} }
@@ -2,34 +2,31 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
namespace MQTTnet.Server public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs
{ {
public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage)
{ {
public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage) SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId));
{ ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
/// <summary>
/// Gets or sets whether the enqueue of the application message should be performed or not.
/// If set to _False_ the client will not receive the application message.
/// </summary>
public bool AcceptEnqueue { get; set; } = true;
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Indicates if the connection with the sender should be closed.
/// </summary>
public bool CloseSenderConnection { get; set; }
public string ReceiverClientId { get; }
public string SenderClientId { get; }
} }
/// <summary>
/// Gets or sets whether the enqueue of the application message should be performed or not.
/// If set to _False_ the client will not receive the application message.
/// </summary>
public bool AcceptEnqueue { get; set; } = true;
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Indicates if the connection with the sender should be closed.
/// </summary>
public bool CloseSenderConnection { get; set; }
public string ReceiverClientId { get; }
public string SenderClientId { get; }
} }
@@ -2,53 +2,60 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Threading; using System.Net;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class InterceptingPacketEventArgs : EventArgs
{ {
public sealed class InterceptingPacketEventArgs : EventArgs public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems)
{ {
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string endpoint, MqttPacket packet, IDictionary sessionItems) CancellationToken = cancellationToken;
{ ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
CancellationToken = cancellationToken; UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); RemoteEndPoint = remoteEndPoint;
Endpoint = endpoint; Packet = packet ?? throw new ArgumentNullException(nameof(packet));
Packet = packet ?? throw new ArgumentNullException(nameof(packet)); SessionItems = sessionItems;
SessionItems = sessionItems;
}
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client ID which has sent the packet or will receive the packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the endpoint of the sending or receiving client.
/// </summary>
public string Endpoint { get; }
/// <summary>
/// Gets or sets the MQTT packet which was received or will be sent.
/// </summary>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <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 the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client ID which has sent the packet or will receive the packet.
/// </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 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.
/// </summary>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <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; }
} }
@@ -2,50 +2,53 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Threading;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class InterceptingPublishEventArgs : EventArgs
{ {
public sealed class InterceptingPublishEventArgs : EventArgs public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems)
{ {
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, IDictionary sessionItems) ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
{ CancellationToken = cancellationToken;
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
CancellationToken = cancellationToken; UserName = userName;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { get; }
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the publish should be processed internally.
/// </summary>
public bool ProcessPublish { get; set; } = true;
/// <summary>
/// Gets the response which will be sent to the client via the PUBACK etc. packets.
/// </summary>
public PublishResponse Response { get; } = new PublishResponse();
/// <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; }
} }
public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the publish should be processed internally.
/// </summary>
public bool ProcessPublish { get; set; } = true;
/// <summary>
/// Gets the response which will be sent to the client via the PUBACK etc. packets.
/// </summary>
public PublishResponse Response { get; } = new PublishResponse();
/// <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; }
} }
@@ -2,83 +2,86 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Threading;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class InterceptingSubscriptionEventArgs : EventArgs
{ {
public sealed class InterceptingSubscriptionEventArgs : EventArgs public InterceptingSubscriptionEventArgs(
CancellationToken cancellationToken,
string clientId,
string userName,
MqttSessionStatus session,
MqttTopicFilter topicFilter,
List<MqttUserProperty> userProperties)
{ {
public InterceptingSubscriptionEventArgs( CancellationToken = cancellationToken;
CancellationToken cancellationToken, ClientId = clientId;
string clientId, UserName = userName;
MqttSessionStatus session, Session = session;
MqttTopicFilter topicFilter, TopicFilter = topicFilter;
List<MqttUserProperty> userProperties) UserProperties = userProperties;
{
CancellationToken = cancellationToken;
ClientId = clientId;
Session = session;
TopicFilter = topicFilter;
UserProperties = userProperties;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
/// </summary>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the broker should create an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessSubscription { get; set; } = true;
/// <summary>
/// Gets or sets the reason string which will be sent to the client in the SUBACK packet.
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// Gets the response which will be sent to the client via the SUBACK packet.
/// </summary>
public SubscribeResponse Response { get; } = new SubscribeResponse();
/// <summary>
/// Gets the current client session.
/// </summary>
public MqttSessionStatus Session { get; }
/// <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 => Session.Items;
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { 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>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the broker should create an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessSubscription { get; set; } = true;
/// <summary>
/// Gets or sets the reason string which will be sent to the client in the SUBACK packet.
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// Gets the response which will be sent to the client via the SUBACK packet.
/// </summary>
public SubscribeResponse Response { get; } = new SubscribeResponse();
/// <summary>
/// Gets the current client session.
/// </summary>
public MqttSessionStatus Session { get; }
/// <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 => Session.Items;
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; set; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
@@ -2,71 +2,73 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Threading;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs
{ {
public sealed class InterceptingUnsubscriptionEventArgs : EventArgs public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, string userName, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties)
{ {
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties) CancellationToken = cancellationToken;
{ ClientId = clientId;
CancellationToken = cancellationToken; UserName = userName;
ClientId = clientId; SessionItems = sessionItems;
SessionItems = sessionItems; Topic = topic;
Topic = topic; UserProperties = userProperties;
UserProperties = userProperties;
}
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { get; }
/// <summary>
/// Gets or sets whether the broker should close the client connection.
/// </summary>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the broker should remove an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessUnsubscription { get; set; } = true;
/// <summary>
/// Gets the response which will be sent to the client via the UNSUBACK pocket.
/// </summary>
public UnsubscribeResponse Response { get; } = new UnsubscribeResponse();
/// <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 MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected
/// client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
/// separator).
/// </summary>
public string Topic { get; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
/// <summary>
/// Gets the cancellation token which can indicate that the client connection gets down.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <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 { 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>
public bool CloseConnection { get; set; }
/// <summary>
/// Gets or sets whether the broker should remove an internal subscription for the client.
/// The broker can also avoid this and return "success" to the client.
/// This feature allows using the MQTT Broker as the Frontend and another system as the backend.
/// </summary>
public bool ProcessUnsubscription { get; set; } = true;
/// <summary>
/// Gets the response which will be sent to the client via the UNSUBACK pocket.
/// </summary>
public UnsubscribeResponse Response { get; } = new UnsubscribeResponse();
/// <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 MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected
/// client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level
/// separator).
/// </summary>
public string Topic { get; }
/// <summary>
/// Gets or sets the user properties.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
} }
@@ -2,13 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
using System.Collections.Generic;
namespace MQTTnet.Server public sealed class LoadingRetainedMessagesEventArgs : EventArgs
{ {
public sealed class LoadingRetainedMessagesEventArgs : EventArgs public List<MqttApplicationMessage> LoadedRetainedMessages { get; set; } = new List<MqttApplicationMessage>();
{
public List<MqttApplicationMessage> LoadedRetainedMessages { get; set; } = new List<MqttApplicationMessage>();
}
} }
@@ -2,50 +2,47 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Server.Internal; using MQTTnet.Server.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class PreparingSessionEventArgs : EventArgs
{ {
public sealed class PreparingSessionEventArgs : EventArgs public string Id { get; set; }
{
public string Id { get; set; }
// TODO: Allow adding of packets to the queue etc. // TODO: Allow adding of packets to the queue etc.
/* /*
* The Session State in the Server consists of: * The Session State in the Server consists of:
· The existence of a Session, even if the rest of the Session State is empty. · The existence of a Session, even if the rest of the Session State is empty.
· The Clients subscriptions, including any Subscription Identifiers. · The Clients subscriptions, including any Subscription Identifiers.
· QoS 1 and QoS 2 messages which have been sent to the Client, but have not been completely acknowledged. · QoS 1 and QoS 2 messages which have been sent to the Client, but have not been completely acknowledged.
· QoS 1 and QoS 2 messages pending transmission to the Client and OPTIONALLY QoS 0 messages pending transmission to the Client. · QoS 1 and QoS 2 messages pending transmission to the Client and OPTIONALLY QoS 0 messages pending transmission to the Client.
· QoS 2 messages which have been received from the Client, but have not been completely acknowledged.The Will Message and the Will Delay Interval · QoS 2 messages which have been received from the Client, but have not been completely acknowledged.The Will Message and the Will Delay Interval
· If the Session is currently not connected, the time at which the Session will end and Session State will be discarded. · If the Session is currently not connected, the time at which the Session will end and Session State will be discarded.
*/ */
public bool IsExistingSession { get; set; } public bool IsExistingSession { get; set; }
public IDictionary<object, object> Items { get; set; } public IDictionary<object, object> Items { get; set; }
public List<MqttPublishPacket> PublishPackets { get; } = new List<MqttPublishPacket>(); public List<MqttPublishPacket> PublishPackets { get; } = new List<MqttPublishPacket>();
DateTime? SessionExpiryTimestamp { get; set; } DateTime? SessionExpiryTimestamp { get; set; }
public List<MqttSubscription> Subscriptions { get; } = new List<MqttSubscription>(); public List<MqttSubscription> Subscriptions { get; } = new List<MqttSubscription>();
/// <summary> /// <summary>
/// Gets the will delay interval. /// Gets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent. /// This is the time between the client disconnect and the time the will message will be sent.
/// </summary> /// </summary>
public uint? WillDelayInterval { get; set; } public uint? WillDelayInterval { get; set; }
// <summary> // <summary>
// Gets the last will message. // Gets the last will message.
// In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client. // In MQTT, you use the last will message feature to notify other clients about an ungracefully disconnected client.
// </summary> // </summary>
// TODO: Use single properties. No entire will message. // TODO: Use single properties. No entire will message.
//MqttApplicationMessage WillMessage { get; set; } //MqttApplicationMessage WillMessage { get; set; }
}
} }
@@ -2,21 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class QueueMessageOverwrittenEventArgs : EventArgs
{ {
public sealed class QueueMessageOverwrittenEventArgs : EventArgs public QueueMessageOverwrittenEventArgs(string receiverClientId, MqttPacket packet)
{ {
public QueueMessageOverwrittenEventArgs(string receiverClientId, MqttPacket packet) ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
{ Packet = packet ?? throw new ArgumentNullException(nameof(packet));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
}
public MqttPacket Packet { get; }
public string ReceiverClientId { get; }
} }
public MqttPacket Packet { get; }
public string ReceiverClientId { get; }
} }
@@ -2,24 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; namespace MQTTnet.Server;
using System.Collections.Generic;
namespace MQTTnet.Server public sealed class RetainedMessageChangedEventArgs : EventArgs
{ {
public sealed class RetainedMessageChangedEventArgs : EventArgs public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages)
{ {
public RetainedMessageChangedEventArgs(string clientId, MqttApplicationMessage changedRetainedMessage, List<MqttApplicationMessage> storedRetainedMessages) ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
{ ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages));
ChangedRetainedMessage = changedRetainedMessage ?? throw new ArgumentNullException(nameof(changedRetainedMessage));
StoredRetainedMessages = storedRetainedMessages ?? throw new ArgumentNullException(nameof(storedRetainedMessages));
}
public MqttApplicationMessage ChangedRetainedMessage { get; }
public string ClientId { get; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; }
} }
public MqttApplicationMessage ChangedRetainedMessage { get; }
public string ClientId { get; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; }
} }
@@ -2,27 +2,31 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
namespace MQTTnet.Server namespace MQTTnet.Server;
public sealed class SessionDeletedEventArgs : EventArgs
{ {
public sealed class SessionDeletedEventArgs : EventArgs public SessionDeletedEventArgs(string id, string userName, IDictionary sessionItems)
{ {
public SessionDeletedEventArgs(string id, IDictionary sessionItems) Id = id ?? throw new ArgumentNullException(nameof(id));
{ UserName = userName;
Id = id ?? throw new ArgumentNullException(nameof(id)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the ID of the session.
/// </summary>
public string Id { get; }
/// <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 the ID of the session.
/// </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>
public IDictionary SessionItems { get; }
} }
@@ -2,189 +2,243 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; 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)); // From RFC: If the initial CONNECT packet included an Authentication Method property then all AUTH packets,
ChannelAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter)); // 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].
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); 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> if (responsePacket is MqttAuthPacket responseAuthPacket)
/// Gets or sets the assigned client identifier. {
/// MQTTv5 only. if (!string.Equals(AuthenticationMethod, responseAuthPacket.AuthenticationMethod, StringComparison.Ordinal))
/// </summary> {
public string AssignedClientIdentifier { get; set; } throw new MqttProtocolViolationException("The authentication method cannot change while authenticating the client.");
}
/// <summary> return ExchangeEnhancedAuthenticationResultFactory.Create(responseAuthPacket);
/// Gets or sets the authentication data. }
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
/// <summary> if (responsePacket is MqttDisconnectPacket disconnectPacket)
/// Gets or sets the authentication method. {
/// <remarks>MQTT 5.0.0+ feature.</remarks> return ExchangeEnhancedAuthenticationResultFactory.Create(disconnectPacket);
/// </summary> }
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary> throw new MqttProtocolViolationException("Received other packet than AUTH while authenticating.");
/// 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;
} }
} }
@@ -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 IDictionary CustomSessionItems { get; set; }
public string SenderClientId { get; set; } = string.Empty; public string SenderClientId { get; set; } = string.Empty;
public string SenderUserName { get; set; }
} }
@@ -8,117 +8,116 @@ using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server.Internal.Adapter namespace MQTTnet.Server.Internal.Adapter;
public sealed class MqttTcpServerAdapter : IMqttServerAdapter
{ {
public sealed class MqttTcpServerAdapter : IMqttServerAdapter readonly List<MqttTcpServerListener> _listeners = new();
CancellationTokenSource _cancellationTokenSource;
MqttServerOptions _serverOptions;
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public bool TreatSocketOpeningErrorAsWarning { get; set; }
public void Dispose()
{ {
readonly List<MqttTcpServerListener> _listeners = new List<MqttTcpServerListener>(); Cleanup();
}
CancellationTokenSource _cancellationTokenSource; public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{
MqttServerOptions _serverOptions; if (_cancellationTokenSource != null)
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public bool TreatSocketOpeningErrorAsWarning { get; set; }
public void Dispose()
{ {
Cleanup(); throw new InvalidOperationException("Server is already started.");
} }
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) _serverOptions = options;
_cancellationTokenSource = new CancellationTokenSource();
if (options.DefaultEndpointOptions.IsEnabled)
{ {
if (_cancellationTokenSource != null) RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token);
}
if (options.TlsEndpointOptions?.IsEnabled == true)
{
if (options.TlsEndpointOptions.CertificateProvider == null)
{ {
throw new InvalidOperationException("Server is already started."); throw new ArgumentException("TLS certificate is not set.");
} }
_serverOptions = options; RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
}
_cancellationTokenSource = new CancellationTokenSource(); return CompletedTask.Instance;
}
if (options.DefaultEndpointOptions.IsEnabled) public Task StopAsync()
{
Cleanup();
return CompletedTask.Instance;
}
void Cleanup()
{
try
{
_cancellationTokenSource?.Cancel(false);
}
finally
{
_cancellationTokenSource?.Dispose();
_cancellationTokenSource = null;
foreach (var listener in _listeners)
{ {
RegisterListeners(options.DefaultEndpointOptions, logger, _cancellationTokenSource.Token); listener.Dispose();
} }
if (options.TlsEndpointOptions?.IsEnabled == true) _listeners.Clear();
{ }
if (options.TlsEndpointOptions.CertificateProvider == null) }
{
throw new ArgumentException("TLS certificate is not set.");
}
RegisterListeners(options.TlsEndpointOptions, logger, _cancellationTokenSource.Token);
}
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter)
{
var clientHandler = ClientHandler;
if (clientHandler == null)
{
return CompletedTask.Instance; return CompletedTask.Instance;
} }
public Task StopAsync() return clientHandler(channelAdapter);
}
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
{
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
{ {
Cleanup(); var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger)
return CompletedTask.Instance;
}
void Cleanup()
{
try
{ {
_cancellationTokenSource?.Cancel(false); ClientHandler = OnClientAcceptedAsync
} };
finally
if (listenerV4.Start(TreatSocketOpeningErrorAsWarning, cancellationToken))
{ {
_cancellationTokenSource?.Dispose(); _listeners.Add(listenerV4);
_cancellationTokenSource = null;
foreach (var listener in _listeners)
{
listener.Dispose();
}
_listeners.Clear();
} }
} }
Task OnClientAcceptedAsync(IMqttChannelAdapter channelAdapter) if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
{ {
var clientHandler = ClientHandler; var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
if (clientHandler == null)
{ {
return CompletedTask.Instance; ClientHandler = OnClientAcceptedAsync
} };
return clientHandler(channelAdapter); if (listenerV6.Start(TreatSocketOpeningErrorAsWarning, cancellationToken))
}
void RegisterListeners(MqttServerTcpEndpointBaseOptions tcpEndpointOptions, IMqttNetLogger logger, CancellationToken cancellationToken)
{
if (!tcpEndpointOptions.BoundInterNetworkAddress.Equals(IPAddress.None))
{ {
var listenerV4 = new MqttTcpServerListener(AddressFamily.InterNetwork, _serverOptions, tcpEndpointOptions, logger) _listeners.Add(listenerV6);
{
ClientHandler = OnClientAcceptedAsync
};
if (listenerV4.Start(TreatSocketOpeningErrorAsWarning, cancellationToken))
{
_listeners.Add(listenerV4);
}
}
if (!tcpEndpointOptions.BoundInterNetworkV6Address.Equals(IPAddress.None))
{
var listenerV6 = new MqttTcpServerListener(AddressFamily.InterNetworkV6, _serverOptions, tcpEndpointOptions, logger)
{
ClientHandler = OnClientAcceptedAsync
};
if (listenerV6.Start(TreatSocketOpeningErrorAsWarning, cancellationToken))
{
_listeners.Add(listenerV6);
}
} }
} }
} }
@@ -12,248 +12,247 @@ using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server.Internal.Adapter namespace MQTTnet.Server.Internal.Adapter;
public sealed class MqttTcpServerListener : IDisposable
{ {
public sealed class MqttTcpServerListener : IDisposable readonly MqttNetSourceLogger _logger;
readonly IMqttNetLogger _rootLogger;
readonly AddressFamily _addressFamily;
readonly MqttServerOptions _serverOptions;
readonly MqttServerTcpEndpointBaseOptions _options;
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
CrossPlatformSocket _socket;
IPEndPoint _localEndPoint;
public MqttTcpServerListener(
AddressFamily addressFamily,
MqttServerOptions serverOptions,
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
IMqttNetLogger logger)
{ {
readonly MqttNetSourceLogger _logger; _addressFamily = addressFamily;
readonly IMqttNetLogger _rootLogger; _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
readonly AddressFamily _addressFamily; _options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
readonly MqttServerOptions _serverOptions; _rootLogger = logger;
readonly MqttServerTcpEndpointBaseOptions _options; _logger = logger.WithSource(nameof(MqttTcpServerListener));
readonly MqttServerTlsTcpEndpointOptions _tlsOptions;
CrossPlatformSocket _socket; if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
IPEndPoint _localEndPoint;
public MqttTcpServerListener(
AddressFamily addressFamily,
MqttServerOptions serverOptions,
MqttServerTcpEndpointBaseOptions tcpEndpointOptions,
IMqttNetLogger logger)
{ {
_addressFamily = addressFamily; _tlsOptions = tlsOptions;
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_options = tcpEndpointOptions ?? throw new ArgumentNullException(nameof(tcpEndpointOptions));
_rootLogger = logger;
_logger = logger.WithSource(nameof(MqttTcpServerListener));
if (_options is MqttServerTlsTcpEndpointOptions tlsOptions)
{
_tlsOptions = tlsOptions;
}
} }
}
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; } public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken) public bool Start(bool treatErrorsAsWarning, CancellationToken cancellationToken)
{
try
{
var boundIp = _options.BoundInterNetworkAddress;
if (_addressFamily == AddressFamily.InterNetworkV6)
{
boundIp = _options.BoundInterNetworkV6Address;
}
_localEndPoint = new IPEndPoint(boundIp, _options.Port);
_logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null);
_socket = new CrossPlatformSocket(_addressFamily, ProtocolType.Tcp);
// Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2
if (_options.ReuseAddress)
{
_socket.ReuseAddress = true;
}
if (_options.NoDelay)
{
_socket.NoDelay = true;
}
if (_options.LingerState != null)
{
_socket.LingerState = _options.LingerState;
}
if (_options.KeepAlive.HasValue)
{
_socket.KeepAlive = _options.KeepAlive.Value;
}
if (_options.TcpKeepAliveInterval.HasValue)
{
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value;
}
if (_options.TcpKeepAliveRetryCount.HasValue)
{
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value;
}
if (_options.TcpKeepAliveTime.HasValue)
{
_socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value;
}
_socket.Bind(_localEndPoint);
// Get the local endpoint back from the socket. The port may have changed.
// This can happen when port 0 is used. Then the OS will choose the next free port.
_localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
_options.Port = _localEndPoint.Port;
_socket.Listen(_options.ConnectionBacklog);
_logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint);
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
return true;
}
catch (Exception exception)
{
if (!treatErrorsAsWarning)
{
throw;
}
_logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint);
return false;
}
}
public void Dispose()
{
_socket?.Dispose();
}
async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{ {
try try
{ {
var boundIp = _options.BoundInterNetworkAddress; var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false);
if (_addressFamily == AddressFamily.InterNetworkV6) if (clientSocket == null)
{ {
boundIp = _options.BoundInterNetworkV6Address; continue;
} }
_localEndPoint = new IPEndPoint(boundIp, _options.Port); _ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
}
_logger.Info("Starting TCP listener (Endpoint={0}, TLS={1})", _localEndPoint, _tlsOptions?.CertificateProvider != null); catch (OperationCanceledException)
{
_socket = new CrossPlatformSocket(_addressFamily, ProtocolType.Tcp);
// Usage of socket options is described here: https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.setsocketoption?view=netcore-2.2
if (_options.ReuseAddress)
{
_socket.ReuseAddress = true;
}
if (_options.NoDelay)
{
_socket.NoDelay = true;
}
if (_options.LingerState != null)
{
_socket.LingerState = _options.LingerState;
}
if (_options.KeepAlive.HasValue)
{
_socket.KeepAlive = _options.KeepAlive.Value;
}
if (_options.TcpKeepAliveInterval.HasValue)
{
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveInterval.Value;
}
if (_options.TcpKeepAliveRetryCount.HasValue)
{
_socket.TcpKeepAliveInterval = _options.TcpKeepAliveRetryCount.Value;
}
if (_options.TcpKeepAliveTime.HasValue)
{
_socket.TcpKeepAliveTime = _options.TcpKeepAliveTime.Value;
}
_socket.Bind(_localEndPoint);
// Get the local endpoint back from the socket. The port may have changed.
// This can happen when port 0 is used. Then the OS will choose the next free port.
_localEndPoint = (IPEndPoint)_socket.LocalEndPoint;
_options.Port = _localEndPoint.Port;
_socket.Listen(_options.ConnectionBacklog);
_logger.Verbose("TCP listener started (Endpoint={0})", _localEndPoint);
Task.Run(() => AcceptClientConnectionsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
return true;
} }
catch (Exception exception) catch (Exception exception)
{ {
if (!treatErrorsAsWarning) if (exception is SocketException socketException)
{ {
throw; if (socketException.SocketErrorCode is SocketError.ConnectionAborted or SocketError.OperationAborted)
}
_logger.Warning(exception, "Error while starting TCP listener (Endpoint={0})", _localEndPoint);
return false;
}
}
public void Dispose()
{
_socket?.Dispose();
}
async Task AcceptClientConnectionsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
var clientSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false);
if (clientSocket == null)
{ {
continue; continue;
} }
}
_ = Task.Factory.StartNew(() => TryHandleClientConnectionAsync(clientSocket), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false); _logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint);
} await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is SocketException socketException)
{
if (socketException.SocketErrorCode == SocketError.ConnectionAborted ||
socketException.SocketErrorCode == SocketError.OperationAborted)
{
continue;
}
}
_logger.Error(exception, "Error while accepting TCP connection (Endpoint={0})", _localEndPoint);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
}
} }
} }
}
async Task TryHandleClientConnectionAsync(CrossPlatformSocket clientSocket)
{ async Task TryHandleClientConnectionAsync(CrossPlatformSocket clientSocket)
Stream stream = null; {
string remoteEndPoint = null; Stream? stream = null;
EndPoint? remoteEndPoint = null;
try
{ try
remoteEndPoint = clientSocket.RemoteEndPoint.ToString(); {
remoteEndPoint = clientSocket.RemoteEndPoint;
_logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint);
_logger.Verbose("TCP client '{0}' accepted (Local endpoint={1})", remoteEndPoint, _localEndPoint);
clientSocket.NoDelay = _options.NoDelay;
stream = clientSocket.GetStream(); clientSocket.NoDelay = _options.NoDelay;
var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate(); stream = clientSocket.GetStream();
var clientCertificate = _tlsOptions?.CertificateProvider?.GetCertificate();
if (clientCertificate != null)
{ if (clientCertificate != null)
if (!clientCertificate.HasPrivateKey) {
{ if (!clientCertificate.HasPrivateKey)
throw new InvalidOperationException("The certificate for TLS encryption must contain the private key."); {
} throw new InvalidOperationException("The certificate for TLS encryption must contain the private key.");
}
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
var sslStream = new SslStream(stream, false, _tlsOptions.RemoteCertificateValidationCallback);
await sslStream.AuthenticateAsServerAsync(
new SslServerAuthenticationOptions await sslStream.AuthenticateAsServerAsync(
{ new SslServerAuthenticationOptions
ServerCertificate = clientCertificate, {
ClientCertificateRequired = _tlsOptions.ClientCertificateRequired, ServerCertificate = clientCertificate,
EnabledSslProtocols = _tlsOptions.SslProtocol, ClientCertificateRequired = _tlsOptions.ClientCertificateRequired,
CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck, EnabledSslProtocols = _tlsOptions.SslProtocol,
EncryptionPolicy = EncryptionPolicy.RequireEncryption, CertificateRevocationCheckMode = _tlsOptions.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
CipherSuitesPolicy = _tlsOptions.CipherSuitesPolicy EncryptionPolicy = EncryptionPolicy.RequireEncryption,
}).ConfigureAwait(false); CipherSuitesPolicy = _tlsOptions.CipherSuitesPolicy
}).ConfigureAwait(false);
stream = sslStream;
stream = sslStream;
clientCertificate = sslStream.RemoteCertificate as X509Certificate2;
clientCertificate = sslStream.RemoteCertificate as X509Certificate2;
if (clientCertificate == null && sslStream.RemoteCertificate != null)
{ if (clientCertificate == null && sslStream.RemoteCertificate != null)
clientCertificate = new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)); {
} clientCertificate = new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert));
} }
}
var clientHandler = ClientHandler;
if (clientHandler != null) var clientHandler = ClientHandler;
{ if (clientHandler != null)
var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate); {
var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax); var tcpChannel = new MqttTcpChannel(stream, remoteEndPoint, clientCertificate);
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter); var bufferWriter = new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax);
var packetFormatterAdapter = new MqttPacketFormatterAdapter(bufferWriter);
using (var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger))
{ using var clientAdapter = new MqttChannelAdapter(tcpChannel, packetFormatterAdapter, _rootLogger);
clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation; clientAdapter.AllowPacketFragmentation = _options.AllowPacketFragmentation;
await clientHandler(clientAdapter).ConfigureAwait(false); await clientHandler(clientAdapter).ConfigureAwait(false);
} }
} }
} catch (Exception exception)
catch (Exception exception) {
{ if (exception is ObjectDisposedException)
if (exception is ObjectDisposedException) {
{ // It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed.
// It can happen that the listener socket is accessed after the cancellation token is already set and the listener socket is disposed. return;
return; }
}
if (exception is SocketException socketException &&
if (exception is SocketException socketException && socketException.SocketErrorCode == SocketError.OperationAborted)
socketException.SocketErrorCode == SocketError.OperationAborted) {
{ return;
return; }
}
_logger.Error(exception, "Error while handling TCP client connection");
_logger.Error(exception, "Error while handling TCP client connection"); }
} finally
finally {
{ try
try {
{ if (stream != null)
// ReSharper disable once MethodHasAsyncOverload {
stream?.Dispose(); await stream.DisposeAsync().ConfigureAwait(false);
clientSocket?.Dispose(); }
}
catch (Exception disposeException) clientSocket?.Dispose();
{ }
_logger.Error(disposeException, "Error while cleaning up client connection"); catch (Exception disposeException)
} {
} _logger.Error(disposeException, "Error while cleaning up client connection");
}
_logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint); }
}
_logger.Verbose("TCP client '{0}' disconnected (Local endpoint={1})", remoteEndPoint, _localEndPoint);
} }
} }
@@ -4,18 +4,17 @@
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal namespace MQTTnet.Server.Internal;
public sealed class CheckSubscriptionsResult
{ {
public sealed class CheckSubscriptionsResult public static CheckSubscriptionsResult NotSubscribed { get; } = new CheckSubscriptionsResult();
{
public static CheckSubscriptionsResult NotSubscribed { get; } = new CheckSubscriptionsResult();
public bool IsSubscribed { get; set; } public bool IsSubscribed { get; set; }
public bool RetainAsPublished { get; set; } public bool RetainAsPublished { get; set; }
public List<uint> SubscriptionIdentifiers { get; set; } public List<uint> SubscriptionIdentifiers { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
}
} }
@@ -4,24 +4,23 @@
using MQTTnet.Packets; using MQTTnet.Packets;
namespace MQTTnet.Server.Internal namespace MQTTnet.Server.Internal;
public sealed class DispatchApplicationMessageResult
{ {
public sealed class DispatchApplicationMessageResult public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List<MqttUserProperty> userProperties)
{ {
public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List<MqttUserProperty> userProperties) ReasonCode = reasonCode;
{ CloseConnection = closeConnection;
ReasonCode = reasonCode; ReasonString = reasonString;
CloseConnection = closeConnection; UserProperties = userProperties;
ReasonString = reasonString;
UserProperties = userProperties;
}
public bool CloseConnection { get; }
public int ReasonCode { get; }
public string ReasonString { get; }
public List<MqttUserProperty> UserProperties { get; }
} }
public bool CloseConnection { get; }
public int ReasonCode { get; }
public string ReasonString { get; }
public List<MqttUserProperty> UserProperties { get; }
} }
@@ -2,11 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MQTTnet.Server.Internal namespace MQTTnet.Server.Internal;
public enum EnqueueDataPacketResult
{ {
public enum EnqueueDataPacketResult Enqueued,
{ Dropped
Enqueued,
Dropped
}
} }
@@ -12,10 +12,7 @@ public static class MqttConnAckPacketFactory
{ {
public static MqttConnAckPacket Create(ValidatingConnectionEventArgs validatingConnectionEventArgs) public static MqttConnAckPacket Create(ValidatingConnectionEventArgs validatingConnectionEventArgs)
{ {
if (validatingConnectionEventArgs == null) ArgumentNullException.ThrowIfNull(validatingConnectionEventArgs);
{
throw new ArgumentNullException(nameof(validatingConnectionEventArgs));
}
var connAckPacket = new MqttConnAckPacket var connAckPacket = new MqttConnAckPacket
{ {
@@ -11,15 +11,8 @@ public static class MqttPubAckPacketFactory
{ {
public static MqttPubAckPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult) public static MqttPubAckPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
{ {
if (publishPacket == null) ArgumentNullException.ThrowIfNull(publishPacket);
{ ArgumentNullException.ThrowIfNull(dispatchApplicationMessageResult);
throw new ArgumentNullException(nameof(publishPacket));
}
if (dispatchApplicationMessageResult == null)
{
throw new ArgumentNullException(nameof(dispatchApplicationMessageResult));
}
var pubAckPacket = new MqttPubAckPacket var pubAckPacket = new MqttPubAckPacket
{ {
@@ -11,10 +11,7 @@ public static class MqttPubCompPacketFactory
{ {
public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode) public static MqttPubCompPacket Create(MqttPubRelPacket pubRelPacket, MqttApplicationMessageReceivedReasonCode reasonCode)
{ {
if (pubRelPacket == null) ArgumentNullException.ThrowIfNull(pubRelPacket);
{
throw new ArgumentNullException(nameof(pubRelPacket));
}
return new MqttPubCompPacket return new MqttPubCompPacket
{ {

Some files were not shown because too many files have changed in this diff Show More