Compare commits

..
Author SHA1 Message Date
christian b477ddbe60 Start using readonly memory 2025-10-22 11:35:56 +02:00
Christian d74d83453f Remove unused usings (#2217) 2025-10-21 13:10:09 +02:00
Christian 99e70fdbbb Update samples (#2216)
Separate the topic template samples from the other samples
2025-10-21 12:33:20 +02:00
Christian 535a4e010c Add validations of variable byte integers (#2214)
* Add validations and add docs

* Update release notes

* Fix build
2025-10-20 14:33:50 +02:00
老九andchristian 37c1eecce5 Add Directory.Build.props. (#2120)
* Add Directory.Build.props.

* Adjust Analysis related properties and add conditional enablement for ContinuousIntegrationBuild.

* Adopt new changes in master

---------

Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2025-10-20 13:32:31 +02:00
Christian e791d9aba4 Expose local endpoint in channel adapter (#2213)
* Expose local endpoint

* Update release notes
2025-10-19 23:06:36 +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
420 changed files with 23450 additions and 24762 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
+58
View File
@@ -0,0 +1,58 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Product>MQTTnet</Product>
<Authors>The contributors of MQTTnet</Authors>
<Company>The contributors of MQTTnet</Company>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<DelaySign>false</DelaySign>
<SignAssembly>false</SignAssembly>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<ImplicitUsings>enable</ImplicitUsings>
<NeutralLanguage>en-US</NeutralLanguage>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>recommended</AnalysisMode>
<AnalysisModeStyle>default</AnalysisModeStyle>
<TestingPlatformShowTestsFailure>true</TestingPlatformShowTestsFailure>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<EnableMSTestRunner>true</EnableMSTestRunner>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageIcon>nuget.png</PackageIcon>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
<ItemGroup Condition="'$(GeneratePackageOnBuild)'=='true'">
<None Include="README.md" Pack="true" PackagePath="" Condition="Exists('README.md')" />
<None Include="..\..\Images\nuget.png" Pack="true" PackagePath="" />
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup Condition="'$(IncludeSymbols)'=='true'">
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
</Project>
+2 -3
View File
@@ -7,11 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\M
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.github\workflows\ci.yml = .github\workflows\ci.yml
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
Directory.Build.props = Directory.Build.props
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
Source\ReleaseNotes.md = Source\ReleaseNotes.md Source\ReleaseNotes.md = Source\ReleaseNotes.md
.github\workflows\ci.yml = .github\workflows\ci.yml
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
@@ -85,6 +86,4 @@ Global
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894} SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection
EndGlobal EndGlobal
+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)
+184 -125
View File
@@ -8,26 +8,56 @@
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
{ {
const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";
public static async Task Clean_Disconnect() public static async Task Clean_Disconnect()
{ {
/* /*
* This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet * This sample disconnects in a clean way. This will send a MQTT DISCONNECT packet
* to the server and close the connection afterwards. * to the server and close the connection afterward.
* *
* See sample _Connect_Client_ for more details. * See sample _Connect_Client_ for more details.
*/ */
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);
@@ -35,9 +65,8 @@ public static class Client_Connection_Samples
// connection is closed in a "not clean" way. See MQTT specification for more details. // connection is closed in a "not clean" way. See MQTT specification for more details.
await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build()); await mqttClient.DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build());
} }
}
public static async Task Connect_Client() public static async Task Connect()
{ {
/* /*
* This sample creates a simple MQTT client and connects to a public broker. * This sample creates a simple MQTT client and connects to a public broker.
@@ -48,8 +77,7 @@ public static class Client_Connection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{
// Use builder classes where possible in this project. // Use builder classes where possible in this project.
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
@@ -68,37 +96,74 @@ public static class Client_Connection_Samples
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
} }
}
public static async Task Connect_Client_Timeout() public static async Task Connect_Using_Enhanced_Authentication()
{ {
/* /*
* This sample creates a simple MQTT client and connects to an invalid broker using a timeout. * This sample uses enhanced authentication (Kerberos) when creating the 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()) /*
{ * Server part...
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build(); */
try var mqttServerFactory = new MqttServerFactory();
var serverOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
var server = mqttServerFactory.CreateMqttServer(serverOptions);
server.ValidatingConnectionAsync += async args =>
{ {
using (var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1))) if (args.AuthenticationMethod == "GS2-KRB5")
{ {
await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token); 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
catch (OperationCanceledException)
{ {
Console.WriteLine("Timeout while connecting."); 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_Client_Using_MQTTv5() public static async Task Connect_Using_MQTTv5()
{ {
/* /*
* This sample creates a simple MQTT client and connects to a public broker using MQTTv5. * This sample creates a simple MQTT client and connects to a public broker using MQTTv5.
@@ -108,8 +173,7 @@ public static class Client_Connection_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").WithProtocolVersion(MqttProtocolVersion.V500).Build(); var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").WithProtocolVersion(MqttProtocolVersion.V500).Build();
// In MQTTv5 the response contains much more information. // In MQTTv5 the response contains much more information.
@@ -119,9 +183,8 @@ public static class Client_Connection_Samples
response.DumpToConsole(); response.DumpToConsole();
} }
}
public static async Task Connect_Client_Using_TLS_1_2() 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 sample creates a simple MQTT client and connects to a public broker using TLS 1.2 encryption.
@@ -131,8 +194,7 @@ public static class Client_Connection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io") var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io")
.WithTlsOptions( .WithTlsOptions(
o => o =>
@@ -146,38 +208,13 @@ public static class Client_Connection_Samples
}) })
.Build(); .Build();
using (var timeout = new CancellationTokenSource(5000)) using var timeout = new CancellationTokenSource(5000);
{
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token); await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected."); Console.WriteLine("The MQTT client is connected.");
} }
}
}
public static async Task Connect_Client_Using_WebSockets() public static async Task Connect_Using_TLS_Encryption()
{
/*
* 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 sample creates a simple MQTT client and connects to a public broker with enabled TLS encryption.
@@ -187,8 +224,7 @@ public static class Client_Connection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
.WithTlsOptions( .WithTlsOptions(
o => o.WithCertificateValidationHandler( o => o.WithCertificateValidationHandler(
@@ -198,15 +234,48 @@ public static class Client_Connection_Samples
.Build(); .Build();
// In MQTTv5 the response contains much more information. // In MQTTv5 the response contains much more information.
using (var timeout = new CancellationTokenSource(5000)) using var timeout = new CancellationTokenSource(5000);
{
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token); var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected."); Console.WriteLine("The MQTT client is connected.");
response.DumpToConsole(); 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() public static async Task Connect_With_Amazon_AWS()
@@ -219,8 +288,7 @@ public static class Client_Connection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker") var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("amazon.web.services.broker")
// Disabling packet fragmentation is very important! // Disabling packet fragmentation is very important!
.WithoutPacketFragmentation() .WithoutPacketFragmentation()
@@ -232,7 +300,6 @@ public static class Client_Connection_Samples
await mqttClient.DisconnectAsync(); await mqttClient.DisconnectAsync();
} }
}
public static async Task Disconnect_Clean() public static async Task Disconnect_Clean()
{ {
@@ -244,8 +311,7 @@ public static class Client_Connection_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);
@@ -254,13 +320,12 @@ public static class Client_Connection_Samples
// Using a reason code requires MQTT version 5.0.0! // Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError); await mqttClient.DisconnectAsync(MqttClientDisconnectOptionsReason.ImplementationSpecificError);
} }
}
public static async Task Disconnect_Non_Clean() public static async Task Disconnect_Non_Clean()
{ {
/* /*
* This sample disconnects from the server without sending a DISCONNECT packet. * This sample disconnects from the server without sending a DISCONNECT packet.
* This way of disconnecting is treated as a non clean disconnect which will * This way of disconnecting is treated as a non-clean disconnect which will
* trigger sending the last will etc. * trigger sending the last will etc.
*/ */
@@ -286,8 +351,7 @@ public static class Client_Connection_Samples
var mqttFactory = new MqttClientFactory(); var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using var mqttClient = mqttFactory.CreateMqttClient();
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883) var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("mqtt.fluux.io", 8883)
.WithTlsOptions( .WithTlsOptions(
o => o =>
@@ -306,12 +370,9 @@ public static class Client_Connection_Samples
.Build(); .Build();
// In MQTTv5 the response contains much more information. // In MQTTv5 the response contains much more information.
using (var timeout = new CancellationTokenSource(5000)) using var timeout = new CancellationTokenSource(5000);
{
await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token); await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
} }
}
}
public static async Task Ping_Server() public static async Task Ping_Server()
{ {
@@ -323,8 +384,7 @@ public static class Client_Connection_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);
@@ -334,20 +394,18 @@ public static class Client_Connection_Samples
Console.WriteLine("The MQTT server replied to the ping request."); Console.WriteLine("The MQTT server replied to the ping request.");
} }
}
public static async Task Reconnect_Using_Event() public static async Task Reconnect_Using_Event()
{ {
/* /*
* This sample shows how to reconnect when the connection was dropped. * This sample shows how to reconnect when the connection was dropped.
* This approach uses one of the events from the client. * 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). * This approach has a risk of deadlocks! Consider using the timer approach (see sample).
*/ */
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();
mqttClient.DisconnectedAsync += async e => mqttClient.DisconnectedAsync += async e =>
@@ -355,13 +413,12 @@ public static class Client_Connection_Samples
if (e.ClientWasConnected) if (e.ClientWasConnected)
{ {
// Use the current options as the new options. // Use the current options as the new options.
await mqttClient.ConnectAsync(mqttClient.Options); await mqttClient.ConnectAsync(mqttClientOptions);
} }
}; };
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
} }
}
public static void Reconnect_Using_Timer() public static void Reconnect_Using_Timer()
{ {
@@ -373,8 +430,7 @@ public static class Client_Connection_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();
_ = Task.Run( _ = Task.Run(
@@ -409,55 +465,58 @@ public static class Client_Connection_Samples
Console.WriteLine("Press <Enter> to exit"); Console.WriteLine("Press <Enter> to exit");
Console.ReadLine(); Console.ReadLine();
} }
}
public static async Task ConnectTls_WithCaFile() 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(); var mqttFactory = new MqttClientFactory();
X509Certificate2Collection caChain = new X509Certificate2Collection(); using var mqttClient = mqttFactory.CreateMqttClient();
caChain.ImportFromPem(mosquitto_org); // from https://test.mosquitto.org/ssl/mosquitto.org.crt var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1").Build();
using (var mqttClient = mqttFactory.CreateMqttClient()) try
{ {
var mqttClientOptions = new MqttClientOptionsBuilder() using var timeoutToken = new CancellationTokenSource(TimeSpan.FromSeconds(1));
.WithTcpServer("test.mosquitto.org", 8883) await mqttClient.ConnectAsync(mqttClientOptions, timeoutToken.Token);
.WithTlsOptions(new MqttClientTlsOptionsBuilder() }
.WithTrustChain(caChain) catch (OperationCanceledException)
.Build()) {
.Build(); Console.WriteLine("Timeout while connecting.");
}
var connAck = await mqttClient.ConnectAsync(mqttClientOptions);
Console.WriteLine("Connected to test.moquitto.org:8883 with CaFile mosquitto.org.crt: " + connAck.ResultCode);
} }
sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
{
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
{
if (eventArgs.AuthenticationMethod != "GS2-KRB5")
{
throw new InvalidOperationException("Wrong authentication method");
} }
const string mosquitto_org = @"
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL
BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG
A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU
BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv
by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE
BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES
MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp
dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg
UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW
Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA
s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH
3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo
E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT
MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV
6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC
6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf
+pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK
sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839
LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE
m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
-----END CERTIFICATE-----
";
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);
}
}
} }
+2 -6
View File
@@ -23,8 +23,7 @@ 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();
@@ -42,7 +41,6 @@ public static class Client_Publish_Samples
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,8 +52,7 @@ 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();
@@ -87,5 +84,4 @@ public static class Client_Publish_Samples
Console.WriteLine("MQTT application message is published."); Console.WriteLine("MQTT application message is published.");
} }
}
} }
+16 -24
View File
@@ -7,7 +7,6 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local // ReSharper disable UnusedMember.Local
using MQTTnet.Extensions.TopicTemplate;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers; using MQTTnet.Samples.Helpers;
@@ -16,8 +15,6 @@ namespace MQTTnet.Samples.Client;
public static class Client_Subscribe_Samples public static class Client_Subscribe_Samples
{ {
static readonly MqttTopicTemplate sampleTemplate = new("mqttnet/samples/topic/{id}");
public static async Task Handle_Received_Application_Message() public static async Task Handle_Received_Application_Message()
{ {
/* /*
@@ -26,8 +23,7 @@ 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();
// Setup message handling before connecting so that queued messages // Setup message handling before connecting so that queued messages
@@ -43,7 +39,7 @@ public static class Client_Subscribe_Samples
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().WithTopicFilter("mqttnet/samples/topic/2").Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -52,18 +48,16 @@ public static class Client_Subscribe_Samples
Console.WriteLine("Press enter to exit."); Console.WriteLine("Press enter to exit.");
Console.ReadLine(); Console.ReadLine();
} }
}
public static async Task Send_Responses() public static async Task Send_Responses()
{ {
/* /*
* This sample subscribes to a topic and sends a response to the broker. This requires at least QoS level 1 to work! * This sample subscribes to a topic and sends a detailed response to the client. This requires at least QoS level 1 to work!
*/ */
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...
@@ -83,7 +77,7 @@ public static class Client_Subscribe_Samples
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().WithTopicFilter("topic/test").Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -92,7 +86,6 @@ public static class Client_Subscribe_Samples
// 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,8 +95,7 @@ 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);
@@ -111,9 +103,10 @@ public static class Client_Subscribe_Samples
// 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")) .WithTopicFilter(t => t.WithTopic("topic/1").WithAtLeastOnceQoS())
.WithTopicTemplate(sampleTemplate.WithParameter("id", "2"), noLocal: true) .WithTopicFilter(t => t.WithTopic("topic/2").WithAtMostOnceQoS())
.WithTopicTemplate(sampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe) .WithTopicFilter(t => t.WithTopic("topic/3").WithNoLocal())
.WithTopicFilter(t => t.WithRetainHandling(MqttRetainHandling.SendAtSubscribe))
.Build(); .Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -123,7 +116,6 @@ public static class Client_Subscribe_Samples
// 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,13 +125,14 @@ 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 topicFilter = mqttFactory.CreateTopicFilterBuilder().WithTopic("mqttnet/samples/topic/2").WithAtLeastOnceQoS();
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -148,9 +141,8 @@ public static class Client_Subscribe_Samples
// 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(IMqttClient mqttClient, CancellationToken shutdownToken)
{ {
/* /*
* This sample shows how to achieve concurrent processing and not have message AutoAcknowledged * This sample shows how to achieve concurrent processing and not have message AutoAcknowledged
@@ -178,7 +170,7 @@ public static class Client_Subscribe_Samples
}; };
} }
static void ConcurrentProcessingWithLimit(CancellationToken shutdownToken, IMqttClient mqttClient) static void ConcurrentProcessingWithLimit(IMqttClient mqttClient, CancellationToken shutdownToken)
{ {
/* /*
* This sample shows how to achieve concurrent processing, with: * This sample shows how to achieve concurrent processing, with:
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using MQTTnet.Extensions.TopicTemplate;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers;
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
namespace MQTTnet.Samples.Client;
public static class Client_Subscribe_TopicTemplate_Samples
{
static readonly MqttTopicTemplate SampleTemplate = new("mqttnet/samples/topic/{id}");
public static async Task Subscribe_Multiple_Topics_With_Template()
{
/*
* This sample subscribes to several topics in a single request.
*
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
*/
var mqttFactory = new MqttClientFactory();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// 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.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicTemplate(SampleTemplate.WithParameter("id", "1"))
.WithTopicTemplate(SampleTemplate.WithParameter("id", "2"), noLocal: true)
.WithTopicTemplate(SampleTemplate.WithParameter("id", "3"), retainHandling: MqttRetainHandling.SendAtSubscribe)
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
public static async Task Subscribe_Topic_With_Template()
{
/*
* This sample subscribes to a topic.
*
* This sample requires the nuget 'MQTTnet.Extensions.TopicTemplate'.
*/
var mqttFactory = new MqttClientFactory();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(SampleTemplate.WithParameter("id", "1")).Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
+5 -8
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Globalization;
using System.Text; using System.Text;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
@@ -26,8 +27,7 @@ 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.");
@@ -37,7 +37,6 @@ public static class Logger_Samples
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,10 +49,10 @@ 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(CultureInfo.InvariantCulture, $">> [{args.LogMessage.Timestamp:O}] [{args.LogMessage.ThreadId}] [{args.LogMessage.Source}] [{args.LogMessage.Level}]: {args.LogMessage.Message}");
if (args.LogMessage.Exception != null) if (args.LogMessage.Exception != null)
{ {
output.AppendLine(args.LogMessage.Exception.ToString()); output.AppendLine(args.LogMessage.Exception.ToString());
@@ -68,8 +67,7 @@ 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.");
@@ -79,7 +77,6 @@ public static class Logger_Samples
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
} }
}
sealed class MyLogger : IMqttNetLogger sealed class MyLogger : IMqttNetLogger
{ {
@@ -20,8 +20,7 @@ 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();
@@ -37,7 +36,6 @@ public static class PackageInspection_Samples
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None); await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
} }
}
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs) static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
{ {
+6 -4
View File
@@ -8,15 +8,17 @@ namespace MQTTnet.Samples.Helpers;
internal static class ObjectExtensions internal static class ObjectExtensions
{ {
static readonly JsonSerializerOptions SerializerOptions = new()
{
WriteIndented = true
};
public static TObject DumpToConsole<TObject>(this TObject @object) public static TObject DumpToConsole<TObject>(this TObject @object)
{ {
var output = "NULL"; var output = "NULL";
if (@object != null) if (@object != null)
{ {
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions output = JsonSerializer.Serialize(@object, SerializerOptions);
{
WriteIndented = true
});
} }
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}"); Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
+1 -10
View File
@@ -2,19 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <NoWarn>$(NoWarn);CA1707</NoWarn>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+3 -2
View File
@@ -2,6 +2,7 @@
// 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.Globalization;
using System.Reflection; using System.Reflection;
Console.BackgroundColor = ConsoleColor.White; Console.BackgroundColor = ConsoleColor.White;
@@ -28,7 +29,7 @@ Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor(); Console.ResetColor();
var input = Console.ReadLine(); var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0"); var selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleClass = sampleClasses[selectedIndex]; var selectedSampleClass = sampleClasses[selectedIndex];
var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList(); var sampleMethods = selectedSampleClass.GetMethods(BindingFlags.Static | BindingFlags.Public).OrderBy(m => m.Name).ToList();
@@ -45,7 +46,7 @@ Console.Write("Please choose sample (press Enter to continue): ");
Console.ResetColor(); Console.ResetColor();
input = Console.ReadLine(); input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0"); selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleMethod = sampleMethods[selectedIndex]; var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine(); Console.WriteLine();
+1 -3
View File
@@ -26,8 +26,7 @@ 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() var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com") .WithTcpServer("broker.hivemq.com")
.Build(); .Build();
@@ -43,7 +42,6 @@ public static class RpcClient_Samples
Console.WriteLine("The RPC call was successful."); Console.WriteLine("The RPC call was successful.");
} }
}
/* /*
* The device must respond to the request using the correct topic. The following C code shows how a * The device must respond to the request using the correct topic. The following C code shows how a
+7 -4
View File
@@ -8,6 +8,7 @@
// ReSharper disable EmptyConstructor // ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local // ReSharper disable MemberCanBeMadeStatic.Local
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -52,14 +53,14 @@ public static class Server_ASP_NET_Samples
// Inject other services via constructor. // Inject other services via constructor.
} }
public Task OnClientConnected(ClientConnectedEventArgs eventArgs) public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
{ {
Console.WriteLine($"Client '{eventArgs.ClientId}' connected."); Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs) public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
{ {
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!"); Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
return Task.CompletedTask; return Task.CompletedTask;
@@ -68,6 +69,7 @@ public static class Server_ASP_NET_Samples
sealed class Startup sealed class Startup
{ {
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController) public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
{ {
app.UseRouting(); app.UseRouting();
@@ -88,11 +90,12 @@ public static class Server_ASP_NET_Samples
* Attach event handlers etc. if required. * Attach event handlers etc. if required.
*/ */
server.ValidatingConnectionAsync += mqttController.ValidateConnection; server.ValidatingConnectionAsync += MqttController.ValidateConnection;
server.ClientConnectedAsync += mqttController.OnClientConnected; server.ClientConnectedAsync += MqttController.OnClientConnected;
}); });
} }
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddHostedMqttServer( services.AddHostedMqttServer(
+2 -4
View File
@@ -25,8 +25,7 @@ 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. // Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e => mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{ {
@@ -37,7 +36,7 @@ public static class Server_Diagnostics_Samples
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;
}; };
@@ -48,5 +47,4 @@ public static class Server_Diagnostics_Samples
await mqttServer.StopAsync(); await mqttServer.StopAsync();
} }
}
} }
+29 -2
View File
@@ -9,6 +9,7 @@
// ReSharper disable MemberCanBeMadeStatic.Local // ReSharper disable MemberCanBeMadeStatic.Local
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Samples.Server; namespace MQTTnet.Samples.Server;
@@ -25,8 +26,7 @@ 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. // Here we only change the topic of the received application message.
@@ -43,5 +43,32 @@ public static class Server_Intercepting_Samples
Console.ReadLine(); Console.ReadLine();
await mqttServer.StopAsync(); await mqttServer.StopAsync();
} }
public static async Task Intercept_Subscription()
{
/*
* This sample starts a simple MQTT server which does not allow to subscribe to '#'
* Start the server and try to subscribe to '#' with a client of choice.
*/
var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
mqttServer.InterceptingSubscriptionAsync += args =>
{
if (args.TopicFilter.Topic.Equals("#", StringComparison.Ordinal))
{
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
}
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
} }
} }
@@ -29,8 +29,7 @@ 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. // Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs => server.LoadingRetainedMessageAsync += async eventArgs =>
{ {
@@ -87,7 +86,6 @@ public static class Server_Retained_Messages_Samples
Console.WriteLine("Press Enter to exit."); Console.WriteLine("Press Enter to exit.");
Console.ReadLine(); 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
{ {
+8 -17
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global // ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
using System.Globalization;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -22,8 +23,7 @@ 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));
@@ -34,7 +34,6 @@ public static class Server_Simple_Samples
await affectedClient.DisconnectAsync(); await affectedClient.DisconnectAsync();
} }
} }
}
public static async Task Publish_Message_From_Broker() public static async Task Publish_Message_From_Broker()
{ {
@@ -44,8 +43,7 @@ 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();
@@ -56,7 +54,6 @@ public static class Server_Simple_Samples
SenderClientId = "SenderClientId" SenderClientId = "SenderClientId"
}); });
} }
}
public static async Task Run_Minimal_Server() public static async Task Run_Minimal_Server()
{ {
@@ -77,8 +74,7 @@ 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.");
@@ -87,7 +83,6 @@ public static class Server_Simple_Samples
// 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,8 +98,7 @@ 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.");
@@ -113,7 +107,6 @@ public static class Server_Simple_Samples
// 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,8 +120,7 @@ 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 // Setup connection validation before starting the server so that there is
// no change to connect without valid credentials. // no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e => mqttServer.ValidatingConnectionAsync += e =>
@@ -158,7 +150,6 @@ public static class Server_Simple_Samples
await mqttServer.StopAsync(); await mqttServer.StopAsync();
} }
}
static async Task<MqttServer> StartMqttServer() static async Task<MqttServer> StartMqttServer()
{ {
@@ -171,7 +162,7 @@ public static class Server_Simple_Samples
return server; return server;
} }
class ConsoleLogger : IMqttNetLogger sealed class ConsoleLogger : IMqttNetLogger
{ {
readonly object _consoleSyncRoot = new(); readonly object _consoleSyncRoot = new();
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
if (parameters?.Length > 0) if (parameters?.Length > 0)
{ {
message = string.Format(message, parameters); message = string.Format(CultureInfo.InvariantCulture, message, parameters);
} }
lock (_consoleSyncRoot) lock (_consoleSyncRoot)
+3 -9
View File
@@ -31,8 +31,7 @@ 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.");
@@ -41,7 +40,6 @@ public static class Server_TLS_Samples
// 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,8 +48,7 @@ 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(
@@ -61,8 +58,7 @@ public static class Server_TLS_Samples
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!,
@@ -70,6 +66,4 @@ public static class Server_TLS_Samples
return pfxCertificate; return pfxCertificate;
} }
}
}
} }
@@ -1,19 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <NoWarn>$(NoWarn);CA1707</NoWarn>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -4,11 +4,11 @@
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MQTTnet.AspTestApp.Pages namespace MQTTnet.AspTestApp.Pages;
public partial class IndexModel : PageModel
{ {
public class IndexModel : PageModel readonly ILogger<IndexModel> _logger;
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger) public IndexModel(ILogger<IndexModel> logger)
{ {
@@ -17,7 +17,9 @@ namespace MQTTnet.AspTestApp.Pages
public void OnGet() public void OnGet()
{ {
LogOnGet();
}
} [LoggerMessage(Level = LogLevel.Information, Message = "OnGet")]
} private partial void LogOnGet();
} }
@@ -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 Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Server; using MQTTnet.Server;
@@ -33,10 +32,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;
@@ -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 MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
@@ -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.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
@@ -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>();
} }
}
} }
@@ -2,26 +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;
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 class EndpointRouterExtensions
{
public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern) public static void MapMqtt(this IEndpointRouteBuilder endpoints, string pattern)
{ {
if (endpoints == null) ArgumentNullException.ThrowIfNull(endpoints);
{
throw new ArgumentNullException(nameof(endpoints));
}
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options => endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{ {
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol; options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
}); });
} }
}
} }
@@ -1,60 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName> <AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace> <RootNamespace>MQTTnet.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <IsAotCompatible>true</IsAotCompatible>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description> <Description>MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol.</Description>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.AspNetCore</PackageId> <PackageId>MQTTnet.AspNetCore</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags> <PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<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>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<FrameworkReference Include="Microsoft.AspNetCore.App"/> <FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'">
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/> <ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
@@ -2,18 +2,17 @@
// 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.Adapter; 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)); ArgumentNullException.ThrowIfNull(options);
switch (options.ChannelOptions) switch (options.ChannelOptions)
{ {
@@ -30,5 +29,4 @@ namespace MQTTnet.AspNetCore
} }
} }
} }
}
} }
@@ -2,13 +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.
using System;
using System.Buffers; using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features; using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
@@ -61,27 +58,6 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
} }
} }
public string Endpoint
{
get
{
// mqtt over tcp
if (_connection.RemoteEndPoint != null)
{
return _connection.RemoteEndPoint.ToString();
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null)
{
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
}
return null;
}
}
public bool IsSecureConnection public bool IsSecureConnection
{ {
get get
@@ -104,8 +80,50 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
} }
} }
public EndPoint LocalEndPoint
{
get
{
// mqtt over tcp
if (_connection.LocalEndPoint != null)
{
return _connection.LocalEndPoint;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.LocalIpAddress != null)
{
return new IPEndPoint(httpFeature.LocalIpAddress, httpFeature.LocalPort);
}
return null;
}
}
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; } public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
public EndPoint RemoteEndPoint
{
get
{
// mqtt over tcp
if (_connection.RemoteEndPoint != null)
{
return _connection.RemoteEndPoint;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
if (httpFeature?.RemoteIpAddress != null)
{
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort);
}
return null;
}
}
public async Task ConnectAsync(CancellationToken cancellationToken) public async Task ConnectAsync(CancellationToken cancellationToken)
{ {
if (_connection is SocketConnection tcp && !tcp.IsConnected) if (_connection is SocketConnection tcp && !tcp.IsConnected)
@@ -232,8 +250,8 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
var span = output.GetSpan(buffer.Length); var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span); buffer.Packet.AsSpan().CopyTo(span);
int offset = buffer.Packet.Count; var offset = buffer.Packet.Count;
buffer.Payload.CopyTo(destination: span.Slice(offset)); buffer.Payload.CopyTo(span.Slice(offset));
output.Advance(buffer.Length); output.Advance(buffer.Length);
} }
} }
@@ -2,8 +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.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -33,15 +31,13 @@ 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; var clientHandler = ClientHandler;
if (clientHandler != null) if (clientHandler != null)
{ {
await clientHandler(adapter).ConfigureAwait(false); await clientHandler(adapter).ConfigureAwait(false);
} }
} }
}
public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger) public Task StartAsync(MqttServerOptions options, IMqttNetLogger logger)
{ {
@@ -2,10 +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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
using MQTTnet.Server; using MQTTnet.Server;
@@ -2,9 +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.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
@@ -13,10 +10,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,12 +23,9 @@ 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.ToLowerInvariant().StartsWith("mqtt", StringComparison.InvariantCulture));
} }
} }
@@ -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.
using System; using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
@@ -26,12 +25,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,14 +39,12 @@ 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
{ {
clientCertificate?.Dispose(); clientCertificate?.Dispose();
@@ -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.Runtime.InteropServices; using System.Runtime.InteropServices;
using MQTTnet.Adapter; using MQTTnet.Adapter;
@@ -22,10 +21,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;
@@ -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 Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -16,15 +15,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 +26,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 +50,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 +75,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 -16
View File
@@ -2,44 +2,35 @@
// 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.Diagnostics; using System.Diagnostics;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
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 +41,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 +55,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);
} }
+14 -12
View File
@@ -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;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
@@ -18,11 +14,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 +36,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 +77,7 @@ public sealed class SocketConnection : ConnectionContext
IsConnected = true; IsConnected = true;
} }
Exception ConnectionAborted() static MqttCommunicationException ConnectionAborted()
{ {
return new MqttCommunicationException("Connection Aborted"); return new MqttCommunicationException("Connection Aborted");
} }
@@ -95,8 +94,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 +121,10 @@ public sealed class SocketConnection : ConnectionContext
{ {
if (_aborted) if (_aborted)
{ {
error = error ?? ConnectionAborted(); error ??= ConnectionAborted();
} }
_application.Output.Complete(error); await _application.Output.CompleteAsync(error);
} }
} }
@@ -216,12 +214,16 @@ public sealed class SocketConnection : ConnectionContext
var flushTask = _application.Output.FlushAsync(); var flushTask = _application.Output.FlushAsync();
FlushResult result;
if (!flushTask.IsCompleted) if (!flushTask.IsCompleted)
{ {
await flushTask; result = await flushTask;
}
else
{
result = flushTask.GetAwaiter().GetResult();
} }
var result = flushTask.GetAwaiter().GetResult();
if (result.IsCompleted) if (result.IsCompleted)
{ {
// Pipe consumer is shut down, do we stop writing // Pipe consumer is shut down, do we stop writing
+6 -2
View File
@@ -2,13 +2,12 @@
// 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.IO.Pipelines; using System.IO.Pipelines;
using System.Net.Sockets; using System.Net.Sockets;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
public sealed class SocketReceiver public sealed class SocketReceiver : IDisposable
{ {
readonly SocketAwaitable _awaitable; readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new(); readonly SocketAsyncEventArgs _eventArgs = new();
@@ -33,4 +32,9 @@ public sealed class SocketReceiver
return _awaitable; return _awaitable;
} }
public void Dispose()
{
_eventArgs.Dispose();
}
} }
+6 -3
View File
@@ -2,9 +2,7 @@
// 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.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net.Sockets; using System.Net.Sockets;
@@ -12,7 +10,7 @@ using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore; namespace MQTTnet.AspNetCore;
public sealed class SocketSender public sealed class SocketSender : IDisposable
{ {
readonly SocketAwaitable _awaitable; readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new(); readonly SocketAsyncEventArgs _eventArgs = new();
@@ -28,6 +26,11 @@ public sealed class SocketSender
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError); _eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
} }
public void Dispose()
{
_eventArgs.Dispose();
}
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers) public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
{ {
if (buffers.IsSingleSegment) if (buffers.IsSingleSegment)
@@ -0,0 +1 @@
[assembly: DoNotParallelize]
@@ -2,19 +2,16 @@
// 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.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; 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)]
[MemoryDiagnoser]
public class AsyncLockBenchmark : BaseBenchmark
{
[Benchmark] [Benchmark]
public async Task Synchronize_100_Tasks() public async Task Synchronize_100_Tasks()
{ {
@@ -43,7 +40,7 @@ namespace MQTTnet.Benchmarks
if (globalI != tasksCount) if (globalI != tasksCount)
{ {
throw new Exception($"Code is broken ({globalI})!"); throw new InvalidOperationException($"Code is broken ({globalI})!");
} }
} }
@@ -52,8 +49,7 @@ namespace MQTTnet.Benchmarks
{ {
var asyncLock = new AsyncLock(); var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource()) using var cancellationToken = new CancellationTokenSource();
{
for (var i = 0; i < 100000; i++) for (var i = 0; i < 100000; i++)
{ {
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false)) using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
@@ -61,6 +57,4 @@ namespace MQTTnet.Benchmarks
} }
} }
} }
}
}
} }
+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
{
}
} }
@@ -2,9 +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.IO;
using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger; using MQTTnet.Diagnostics.Logger;
@@ -12,11 +9,11 @@ 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, IDisposable
{ {
[MemoryDiagnoser]
public sealed class ChannelAdapterBenchmark : BaseBenchmark
{
MqttChannelAdapter _channelAdapter; MqttChannelAdapter _channelAdapter;
int _iterations; int _iterations;
MqttPublishPacket _packet; MqttPublishPacket _packet;
@@ -86,5 +83,10 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray(); return buffer.ToArray();
} }
public void Dispose()
{
_channelAdapter?.Dispose();
_stream?.Dispose();
} }
} }
+8 -8
View File
@@ -2,17 +2,18 @@
// 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)]
[RPlotExporter]
[MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark
{
MqttNetNullLogger _nullLogger; MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger; MqttNetSourceLogger _sourceNullLogger;
@@ -42,7 +43,7 @@ namespace MQTTnet.Benchmarks
{ {
if (_useHandler) if (_useHandler)
{ {
eventArgs.LogMessage.ToString(); Debug.WriteLine(eventArgs.LogMessage.ToString());
} }
} }
@@ -87,5 +88,4 @@ namespace MQTTnet.Benchmarks
_sourceNullLogger.Verbose("test log message {0}", "parameter"); _sourceNullLogger.Verbose("test log message {0}", "parameter");
} }
} }
}
} }
@@ -1,24 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<DebugType>Full</DebugType> <DebugType>Full</DebugType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12"/> <PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -1,18 +1,18 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
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;
{
const int max_length = 1024 * 8;
private byte[] source;
private byte[] target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)] [Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; } public int Length { get; set; }
@@ -20,21 +20,20 @@ namespace MQTTnet.Benchmarks
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
source = new byte[max_length]; _source = new byte[MaxLength];
target = new byte[max_length]; _target = new byte[MaxLength];
} }
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Array_Copy() public void Array_Copy()
{ {
Array.Copy(source, 0, target, 0, Length); Array.Copy(_source, 0, _target, 0, Length);
} }
[Benchmark] [Benchmark]
public void Memory_Copy() public void Memory_Copy()
{ {
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length); Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
} }
}
} }
@@ -2,11 +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.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Server; using MQTTnet.Server;
@@ -14,7 +9,7 @@ using MQTTnet.Server;
namespace MQTTnet.Benchmarks; namespace MQTTnet.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class MessageDeliveryBenchmark : BaseBenchmark public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
{ {
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
CancellationTokenSource _cancellationTokenSource; CancellationTokenSource _cancellationTokenSource;
@@ -23,22 +18,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 +56,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 +71,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 +88,29 @@ 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 InvalidOperationException($"Messages Received Count mismatch, expected {_messagesExpectedCount}, received {_messagesReceivedCount}");
} }
} }
public void Dispose()
{
_cancellationTokenSource?.Dispose();
_mqttServer?.Dispose();
}
[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 +120,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 +132,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 +152,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 +171,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 InvalidOperationException(
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,12 +9,12 @@ 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)]
[MemoryDiagnoser]
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
{
IWebHost _host; IWebHost _host;
IMqttClient _mqttClient; IMqttClient _mqttClient;
MqttApplicationMessage _message; MqttApplicationMessage _message;
@@ -30,7 +30,7 @@ namespace MQTTnet.Benchmarks
.AddMqttConnectionHandler(); .AddMqttConnectionHandler();
}) })
.Configure(app => { .Configure(app => {
app.UseMqttServer(s => { app.UseMqttServer(_ => {
}); });
}) })
@@ -69,5 +69,4 @@ namespace MQTTnet.Benchmarks
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult(); _mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
} }
} }
}
} }
@@ -2,18 +2,17 @@
// 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.Text; using System.Text;
using BenchmarkDotNet.Attributes; 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)]
[MemoryDiagnoser]
public class MqttBufferReaderBenchmark
{
byte[] _buffer; byte[] _buffer;
int _bufferLength; int _bufferLength;
@@ -39,5 +38,4 @@ namespace MQTTnet.Benchmarks
{ {
Encoding.UTF8.GetString(_buffer, 0, _bufferLength); Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
} }
}
} }
@@ -2,18 +2,17 @@
// 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;
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)]
[MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
{
readonly byte[] _demoPayload = new byte[1024]; readonly byte[] _demoPayload = new byte[1024];
byte[] _readPayload; byte[] _readPayload;
@@ -92,5 +91,4 @@ namespace MQTTnet.Benchmarks
writer.Reset(0); writer.Reset(0);
} }
} }
}
} }
@@ -2,11 +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.
using System;
using System.Buffers; using System.Buffers;
using System.Reflection; using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Channel; using MQTTnet.Channel;
@@ -19,9 +16,9 @@ namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MqttTcpChannelBenchmark : BaseBenchmark public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
{ {
IMqttChannel _clientChannel; MqttTcpChannel _clientChannel;
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttChannel _serverChannel; IMqttChannel _serverChannel;
@@ -85,4 +82,11 @@ public class MqttTcpChannelBenchmark : BaseBenchmark
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false); await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
} }
} }
public void Dispose()
{
_clientChannel?.Dispose();
_mqttServer?.Dispose();
_serverChannel?.Dispose();
}
} }
+4 -10
View File
@@ -2,17 +2,12 @@
// 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 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 int _selectedBenchmarkIndex;
static List<Type> _benchmarks; static List<Type> _benchmarks;
@@ -94,7 +89,7 @@ namespace MQTTnet.Benchmarks
return; return;
} }
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0])); _selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0], StringComparison.Ordinal));
if (_selectedBenchmarkIndex < 0) if (_selectedBenchmarkIndex < 0)
{ {
@@ -138,5 +133,4 @@ namespace MQTTnet.Benchmarks
Console.WriteLine(); Console.WriteLine();
} }
}
} }
@@ -1,31 +1,25 @@
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.Pipelines; using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public sealed class ReaderExtensionsBenchmark : IDisposable, IAsyncDisposable
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttPacketFormatterAdapter _mqttPacketFormatter;
[RPlotExporter, RankColumn] MemoryStream _stream;
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
{ {
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535)); _mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder() var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic") .WithTopic("topic")
.WithPayload(new byte[10 * 1024]) .WithPayload(new byte[10 * 1024])
@@ -33,66 +27,18 @@ namespace MQTTnet.Benchmarks
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)]
public async Task Before()
{
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_Before.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);
}
}
} }
[Benchmark] [Benchmark]
public async Task After() public async Task After()
{ {
stream.Position = 0; _stream.Position = 0;
var input = PipeReader.Create(stream); var input = PipeReader.Create(_stream);
while (true) while (true)
{ {
@@ -116,7 +62,7 @@ namespace MQTTnet.Benchmarks
{ {
if (!buffer.IsEmpty) if (!buffer.IsEmpty)
{ {
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received)) if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
{ {
break; break;
} }
@@ -136,105 +82,16 @@ namespace MQTTnet.Benchmarks
} }
} }
public static class ReaderExtensions_Before public void Dispose()
{ {
public static bool TryDecode(MqttPacketFormatterAdapter formatter, _stream?.Dispose();
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]; public async ValueTask DisposeAsync()
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{ {
return false; if (_stream != null)
}
if (copy.Length < bodyLength)
{ {
return false; await _stream.DisposeAsync();
} }
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;
}
}
} }
} }
@@ -1,15 +1,14 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class RoundtripProcessingBenchmark : BaseBenchmark
{
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
{ {
@@ -24,7 +23,6 @@ namespace MQTTnet.Benchmarks
[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();
}
} }
} }
@@ -1,57 +1,67 @@
using System.Buffers;
using System.IO.Pipelines;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[RankColumn]
[MemoryDiagnoser]
public sealed class SendPacketAsyncBenchmark : BaseBenchmark, IDisposable, IAsyncDisposable
{ {
[SimpleJob(RuntimeMoniker.Net60)] MqttPacketBuffer _buffer;
[RPlotExporter, RankColumn] MemoryStream _stream;
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark
{
MemoryStream stream;
MqttPacketBuffer buffer;
[GlobalSetup] [Benchmark]
public void GlobalSetup() public async ValueTask After()
{ {
stream = new MemoryStream(1024); _stream.Position = 0;
var packet = new ArraySegment<byte>(new byte[10]); var output = PipeWriter.Create(_stream);
buffer = new MqttPacketBuffer(packet);
if (_buffer.Payload.Length == 0)
{
await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
}
else
{
WritePacketBuffer(output, _buffer);
await output.FlushAsync().ConfigureAwait(false);
}
} }
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public async ValueTask Before() public async ValueTask Before()
{ {
stream.Position = 0; _stream.Position = 0;
var output = PipeWriter.Create(stream); var output = PipeWriter.Create(_stream);
WritePacketBuffer(output, buffer); WritePacketBuffer(output, _buffer);
await output.FlushAsync(); await output.FlushAsync();
} }
[Benchmark] public void Dispose()
public async ValueTask After()
{ {
stream.Position = 0; _stream?.Dispose();
var output = PipeWriter.Create(stream);
if (buffer.Payload.Length == 0)
{
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
} }
else
public async ValueTask DisposeAsync()
{ {
WritePacketBuffer(output, buffer); if (_stream != null)
await output.FlushAsync().ConfigureAwait(false); {
await _stream.DisposeAsync();
} }
} }
[GlobalSetup]
public void GlobalSetup()
{
_stream = new MemoryStream(1024);
var packet = new ArraySegment<byte>(new byte[10]);
_buffer = new MqttPacketBuffer(packet);
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{ {
@@ -61,9 +71,8 @@ namespace MQTTnet.Benchmarks
var span = output.GetSpan(buffer.Length); var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span); buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count)); buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
output.Advance(buffer.Length); output.Advance(buffer.Length);
} }
}
} }
@@ -4,10 +4,7 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Packets; using MQTTnet.Packets;
using System;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -15,17 +12,18 @@ 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)]
[RPlotExporter]
[MemoryDiagnoser]
public class SerializerBenchmark : BaseBenchmark
{
MqttPacket _packet; MqttPacket _packet;
ArraySegment<byte> _serializedPacket; ArraySegment<byte> _serializedPacket;
IMqttPacketFormatter _serializer; MqttV3PacketFormatter _serializer;
MqttBufferWriter _bufferWriter; MqttBufferWriter _bufferWriter;
[GlobalSetup] [GlobalSetup]
@@ -42,7 +40,7 @@ namespace MQTTnet.Benchmarks
} }
[Benchmark] [Benchmark]
public void Serialize_10000_Messages() public void Serialize10000Messages()
{ {
for (var i = 0; i < 10000; i++) for (var i = 0; i < 10000; i++)
{ {
@@ -52,7 +50,7 @@ namespace MQTTnet.Benchmarks
} }
[Benchmark] [Benchmark]
public void Deserialize_10000_Messages() public void Deserialize10000Messages()
{ {
var channel = new BenchmarkMqttChannel(_serializedPacket); var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger()); var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
@@ -61,11 +59,11 @@ namespace MQTTnet.Benchmarks
{ {
channel.Reset(); channel.Reset();
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult(); reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
} }
} }
class BenchmarkMqttChannel : IMqttChannel sealed class BenchmarkMqttChannel : IMqttChannel
{ {
readonly ArraySegment<byte> _buffer; readonly ArraySegment<byte> _buffer;
int _position; int _position;
@@ -76,11 +74,13 @@ namespace MQTTnet.Benchmarks
_position = _buffer.Offset; _position = _buffer.Offset;
} }
public string Endpoint { get; } = string.Empty; public EndPoint RemoteEndPoint { get; set; }
public bool IsSecureConnection { get; } = false; public EndPoint LocalEndPoint { get; set; }
public X509Certificate2 ClientCertificate { get; } public bool IsSecureConnection { get; }
public X509Certificate2 ClientCertificate { get; set; }
public void Reset() public void Reset()
{ {
@@ -99,7 +99,7 @@ namespace MQTTnet.Benchmarks
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
Array.Copy(_buffer.Array, _position, buffer, offset, count); Array.Copy(_buffer!.Array!, _position, buffer, offset, count);
_position += count; _position += count;
return Task.FromResult(count); return Task.FromResult(count);
@@ -114,5 +114,4 @@ namespace MQTTnet.Benchmarks
{ {
} }
} }
}
} }
@@ -5,15 +5,14 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MQTTnet.Tests.Server;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{ {
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark
{
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
{ {
@@ -28,13 +27,12 @@ namespace MQTTnet.Benchmarks
[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();
}
} }
} }
@@ -2,8 +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.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Server; using MQTTnet.Server;
@@ -30,7 +28,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();
@@ -5,18 +5,16 @@
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; 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)]
[MemoryDiagnoser]
public class TcpPipesBenchmark : BaseBenchmark
{
IDuplexPipe _client; IDuplexPipe _client;
IDuplexPipe _server; IDuplexPipe _server;
@@ -76,5 +74,4 @@ namespace MQTTnet.Benchmarks
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false); await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
} }
} }
}
} }
@@ -2,19 +2,16 @@
// 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
{
static readonly char[] TopicLevelSeparator = { '/' };
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark
{
readonly string _longTopic = readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"; "AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
@@ -33,245 +30,4 @@ namespace MQTTnet.Benchmarks
MqttTopicFilterComparer.Compare(_longTopic, _longTopic); 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;
}
}
}
} }
+2 -6
View File
@@ -1,10 +1,7 @@
using System; namespace MQTTnet.Benchmarks;
using System.Collections.Generic;
namespace MQTTnet.Benchmarks public class TopicGenerator
{ {
public class TopicGenerator
{
public static void Generate( public static void Generate(
int numPublishers, int numPublishers,
int numTopicsPerPublisher, int numTopicsPerPublisher,
@@ -80,5 +77,4 @@ namespace MQTTnet.Benchmarks
topicList.Add(topic); topicList.Add(topic);
} }
}
} }
@@ -2,8 +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.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using MQTTnet.Server; using MQTTnet.Server;
@@ -14,15 +12,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();
@@ -2,20 +2,15 @@
// 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.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)
{ {
if (context == null) ArgumentNullException.ThrowIfNull(context);
{
throw new ArgumentNullException(nameof(context));
}
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#")) if (context.MethodName.Contains('/') || context.MethodName.Contains('+') || context.MethodName.Contains('#'))
{ {
throw new ArgumentException("The method name cannot contain /, + or #."); throw new ArgumentException("The method name cannot contain /, + or #.");
} }
@@ -29,5 +24,4 @@ namespace MQTTnet.Extensions.Rpc
ResponseTopic = responseTopic ResponseTopic = responseTopic
}; };
} }
}
} }
@@ -2,18 +2,13 @@
// 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 System.Threading;
using System.Threading.Tasks;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc;
public interface IMqttRpcClient : IDisposable
{ {
public interface IMqttRpcClient : IDisposable
{
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null); 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); 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);
}
} }
@@ -1,54 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName> <AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace> <RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</Description> <Description>This is an extension library which allows executing synchronous device calls including a response using MQTTnet.</Description>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Extensions.Rpc</PackageId> <PackageId>MQTTnet.Extensions.Rpc</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags> <PackageTags>MQTT Message Queue Telemetry Transport MQTTClient MQTTServer Server MQTTBroker Broker NETStandard IoT InternetOfThings Messaging Hardware Arduino Sensor Actuator M2M ESP Smart Home Cities Automation Xamarin Blazor</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources> <EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<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>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/> <ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup> </ItemGroup>
@@ -2,12 +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.
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( return clientFactory.CreateMqttRpcClient(
@@ -20,17 +18,9 @@ namespace MQTTnet.Extensions.Rpc
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{ {
if (mqttClient == null) ArgumentNullException.ThrowIfNull(mqttClient);
{ ArgumentNullException.ThrowIfNull(rpcClientOptions);
throw new ArgumentNullException(nameof(mqttClient));
}
if (rpcClientOptions == null)
{
throw new ArgumentNullException(nameof(rpcClientOptions));
}
return new MqttRpcClient(mqttClient, rpcClientOptions); return new MqttRpcClient(mqttClient, rpcClientOptions);
} }
}
} }
+5 -16
View File
@@ -2,22 +2,17 @@
// 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.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; 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 IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options; readonly MqttRpcClientOptions _options;
@@ -45,8 +40,7 @@ namespace MQTTnet.Extensions.Rpc
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null) public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{ {
using (var timeoutToken = new CancellationTokenSource(timeout)) using var timeoutToken = new CancellationTokenSource(timeout);
{
try try
{ {
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false); return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
@@ -61,14 +55,10 @@ namespace MQTTnet.Extensions.Rpc
throw; throw;
} }
} }
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default) public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{ {
if (methodName == null) ArgumentNullException.ThrowIfNull(methodName);
{
throw new ArgumentNullException(nameof(methodName));
}
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel); var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
@@ -140,5 +130,4 @@ namespace MQTTnet.Extensions.Rpc
return CompletedTask.Instance; return CompletedTask.Instance;
} }
}
} }
@@ -2,23 +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 System.Collections.Generic;
using System.Text; using System.Text;
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)); ArgumentNullException.ThrowIfNull(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();
}
} }
@@ -2,12 +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.
using System; namespace MQTTnet.Extensions.Rpc;
namespace MQTTnet.Extensions.Rpc public sealed class MqttRpcClientOptionsBuilder
{ {
public sealed class MqttRpcClientOptionsBuilder
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy(); IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptions Build() public MqttRpcClientOptions Build()
@@ -24,5 +22,4 @@ namespace MQTTnet.Extensions.Rpc
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 sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; } public string RequestTopic { get; set; }
public string ResponseTopic { get; set; } public string ResponseTopic { get; set; }
}
} }
@@ -2,14 +2,12 @@
// 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.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, public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel) IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{ {
@@ -38,5 +36,4 @@ namespace MQTTnet.Extensions.Rpc
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary> /// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
} }
@@ -1,53 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName> <AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName>
<RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace> <RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description> <Description>
Provides mqtt topic templating logic to support dispatch, Provides mqtt topic templating logic to support dispatch,
routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible). routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible).
</Description> </Description>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>The contributors of MQTTnet</Authors>
<PackageId>MQTTnet.Extensions.TopicTemplate</PackageId> <PackageId>MQTTnet.Extensions.TopicTemplate</PackageId>
<SignAssembly>false</SignAssembly>
<DelaySign>false</DelaySign>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Copyright>Copyright (c) .NET Foundation and Contributors</Copyright>
<PackageProjectUrl>https://github.com/dotnet/MQTTnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/dotnet/MQTTnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>MQTT Message Queue Telemetry MQTTClient Messaging Routing AsyncAPI Template</PackageTags> <PackageTags>MQTT Message Queue Telemetry MQTTClient Messaging Routing AsyncAPI Template</PackageTags>
<NeutralLanguage>en-US</NeutralLanguage>
<PackageIcon>nuget.png</PackageIcon>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<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>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="README.md"> <PackageReference Include="System.ValueTuple" Version="4.6.1" />
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\..\Images\nuget.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -2,11 +2,7 @@
// 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 System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Extensions.TopicTemplate; namespace MQTTnet.Extensions.TopicTemplate;
@@ -41,10 +37,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);
@@ -87,13 +80,13 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
{ {
var filter = TopicFilter; var filter = TopicFilter;
// append slash if neccessary // append slash if neccessary
if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString())) if (filter.Length > 0 && !filter.EndsWith(MqttTopicFilterComparer.LevelSeparator) && !filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
{ {
filter += MqttTopicFilterComparer.LevelSeparator; filter += MqttTopicFilterComparer.LevelSeparator;
} }
// append hash if neccessary // append hash if neccessary
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString())) if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
{ {
filter += MqttTopicFilterComparer.MultiLevelWildcard; filter += MqttTopicFilterComparer.MultiLevelWildcard;
} }
@@ -164,12 +157,12 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString()); return new MqttTopicTemplate(MqttTopicFilterComparer.MultiLevelWildcard.ToString());
} }
if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()) && !root.EndsWith("}")) if (root.Contains(MqttTopicFilterComparer.LevelSeparator) && !root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString(), StringComparison.InvariantCulture) && !root.EndsWith('}'))
{ {
root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator) + 1); root = root.Substring(0, root.LastIndexOf(MqttTopicFilterComparer.LevelSeparator) + 1);
} }
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString())) if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString(), StringComparison.InvariantCulture))
{ {
root += MqttTopicFilterComparer.SingleLevelWildcard; root += MqttTopicFilterComparer.SingleLevelWildcard;
} }
@@ -233,7 +226,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 +326,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);
@@ -2,8 +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.Linq;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -5,10 +5,10 @@
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>
@@ -28,5 +28,4 @@ namespace MQTTnet.Server
/// <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,10 +5,10 @@
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(); readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
public MqttServerClientDisconnectOptions Build() public MqttServerClientDisconnectOptions Build()
@@ -50,5 +50,4 @@ namespace MQTTnet.Server
_options.UserProperties.Add(new MqttUserProperty(name, value)); _options.UserProperties.Add(new MqttUserProperty(name, value));
return this; 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,67 @@
// 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)
{
ArgumentNullException.ThrowIfNull(name);
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
}
@@ -0,0 +1,12 @@
using MQTTnet.Packets;
namespace MQTTnet.Server.EnhancedAuthentication;
public sealed class ExchangeEnhancedAuthenticationResult
{
public string ReasonString { get; set; }
public List<MqttUserProperty> UserProperties { get; set; }
public byte[] AuthenticationData { get; set; }
}
@@ -0,0 +1,34 @@
using MQTTnet.Packets;
namespace MQTTnet.Server.EnhancedAuthentication;
public static class ExchangeEnhancedAuthenticationResultFactory
{
public static ExchangeEnhancedAuthenticationResult Create(MqttAuthPacket authPacket)
{
ArgumentNullException.ThrowIfNull(authPacket);
return new ExchangeEnhancedAuthenticationResult
{
AuthenticationData = authPacket.AuthenticationData,
ReasonString = authPacket.ReasonString,
UserProperties = authPacket.UserProperties
};
}
public static ExchangeEnhancedAuthenticationResult Create(MqttDisconnectPacket disconnectPacket)
{
ArgumentNullException.ThrowIfNull(disconnectPacket);
return new ExchangeEnhancedAuthenticationResult
{
AuthenticationData = null,
ReasonString = disconnectPacket.ReasonString,
UserProperties = disconnectPacket.UserProperties
// SessionExpiryInterval makes no sense because the connection is not yet made!
// ServerReferences makes no sense when the client initiated a DISCONNECT!
};
}
}
@@ -2,12 +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.
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)); SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
@@ -23,5 +21,4 @@ namespace MQTTnet.Server
public bool IsDropped { get; } public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; } public MqttApplicationMessage ApplicationMessage { get; }
}
} }
@@ -2,12 +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.
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)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
@@ -23,5 +21,4 @@ namespace MQTTnet.Server
/// Gets the ID of the client which has sent the affected application message. /// Gets the ID of the client which has sent the affected application message.
/// </summary> /// </summary>
public string SenderId { get; } public string SenderId { get; }
}
} }
@@ -2,17 +2,17 @@
// 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)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
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));
@@ -28,6 +28,11 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2. /// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary> /// </summary>
@@ -42,5 +47,4 @@ namespace MQTTnet.Server
/// Gets the session items which contain custom user data per session. /// Gets the session items which contain custom user data per session.
/// </summary> /// </summary>
public IDictionary SessionItems { get; } public IDictionary SessionItems { get; }
}
} }
@@ -2,23 +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;
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; readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, string endpoint, IDictionary sessionItems) public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
{ {
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion; ProtocolVersion = protocolVersion;
Endpoint = endpoint; RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
} }
@@ -35,7 +35,10 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets the endpoint of the connected client. /// Gets the endpoint of the connected client.
/// </summary> /// </summary>
public string Endpoint { get; } public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary> /// <summary>
/// Gets the protocol version which is used by the connected client. /// Gets the protocol version which is used by the connected client.
@@ -52,10 +55,14 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string UserName => _connectPacket.Username; public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the connected client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
/// <summary> /// <summary>
/// Gets the user properties sent by the client. /// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties; public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
}
} }
@@ -2,30 +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;
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; readonly MqttDisconnectPacket _disconnectPacket;
public ClientDisconnectedEventArgs( public ClientDisconnectedEventArgs(
string clientId, MqttConnectPacket connectPacket,
MqttDisconnectPacket disconnectPacket, MqttDisconnectPacket disconnectPacket,
MqttClientDisconnectType disconnectType, MqttClientDisconnectType disconnectType,
string endpoint, EndPoint remoteEndPoint,
IDictionary sessionItems) IDictionary sessionItems)
{ {
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
DisconnectType = disconnectType; DisconnectType = disconnectType;
Endpoint = endpoint; RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
_connectPacket = connectPacket;
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover. // The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
_disconnectPacket = disconnectPacket; _disconnectPacket = disconnectPacket;
} }
@@ -34,11 +35,24 @@ namespace MQTTnet.Server
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
public MqttClientDisconnectType DisconnectType { get; } public MqttClientDisconnectType DisconnectType { get; }
public string Endpoint { get; } public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary> /// <summary>
/// Gets the reason code sent by the client. /// Gets the reason code sent by the client.
@@ -72,5 +86,4 @@ namespace MQTTnet.Server
/// <remarks>MQTT 5.0.0+ feature.</remarks> /// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary> /// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties; public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
}
} }
@@ -2,17 +2,17 @@
// 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)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
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));
} }
@@ -23,6 +23,11 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
@@ -33,5 +38,4 @@ namespace MQTTnet.Server
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public MqttTopicFilter TopicFilter { get; } public MqttTopicFilter TopicFilter { get; }
}
} }
@@ -2,16 +2,16 @@
// 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)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
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));
} }
@@ -22,6 +22,11 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
@@ -32,5 +37,4 @@ namespace MQTTnet.Server
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public string TopicFilter { get; } public string TopicFilter { get; }
}
} }
@@ -2,12 +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.
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)); SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId));
@@ -31,5 +29,4 @@ namespace MQTTnet.Server
public string ReceiverClientId { get; } public string ReceiverClientId { get; }
public string SenderClientId { get; } public string SenderClientId { get; }
}
} }
@@ -2,20 +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;
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(string clientId, string userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems, CancellationToken cancellationToken)
{
public InterceptingPacketEventArgs(CancellationToken cancellationToken, string clientId, string endpoint, MqttPacket packet, IDictionary sessionItems)
{ {
CancellationToken = cancellationToken; CancellationToken = cancellationToken;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
Endpoint = endpoint; UserName = userName;
RemoteEndPoint = remoteEndPoint;
Packet = packet ?? throw new ArgumentNullException(nameof(packet)); Packet = packet ?? throw new ArgumentNullException(nameof(packet));
SessionItems = sessionItems; SessionItems = sessionItems;
} }
@@ -31,10 +31,18 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets the endpoint of the sending or receiving client. /// Gets the endpoint of the sending or receiving client.
/// </summary> /// </summary>
public string Endpoint { get; } public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary> /// <summary>
/// Gets or sets the MQTT packet which was received or will be sent. /// Gets or sets the MQTT packet which was received or will be sent.
@@ -50,5 +58,4 @@ namespace MQTTnet.Server
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary SessionItems { get; } public IDictionary SessionItems { get; }
}
} }
@@ -2,19 +2,18 @@
// 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, string clientId, string userName, IDictionary sessionItems, CancellationToken cancellationToken)
{
public InterceptingPublishEventArgs(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken, string clientId, IDictionary sessionItems)
{ {
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage)); ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
CancellationToken = cancellationToken; CancellationToken = cancellationToken;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems)); SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
} }
@@ -41,11 +40,15 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets the response which will be sent to the client via the PUBACK etc. packets. /// Gets the response which will be sent to the client via the PUBACK etc. packets.
/// </summary> /// </summary>
public PublishResponse Response { get; } = new PublishResponse(); public PublishResponse Response { get; } = new();
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary SessionItems { get; } public IDictionary SessionItems { get; }
}
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
} }
@@ -2,25 +2,24 @@
// 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( public InterceptingSubscriptionEventArgs(
CancellationToken cancellationToken,
string clientId, string clientId,
string userName,
MqttSessionStatus session, MqttSessionStatus session,
MqttTopicFilter topicFilter, MqttTopicFilter topicFilter,
List<MqttUserProperty> userProperties) List<MqttUserProperty> userProperties,
CancellationToken cancellationToken)
{ {
CancellationToken = cancellationToken; CancellationToken = cancellationToken;
ClientId = clientId; ClientId = clientId;
UserName = userName;
Session = session; Session = session;
TopicFilter = topicFilter; TopicFilter = topicFilter;
UserProperties = userProperties; UserProperties = userProperties;
@@ -57,7 +56,7 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets the response which will be sent to the client via the SUBACK packet. /// Gets the response which will be sent to the client via the SUBACK packet.
/// </summary> /// </summary>
public SubscribeResponse Response { get; } = new SubscribeResponse(); public SubscribeResponse Response { get; } = new();
/// <summary> /// <summary>
/// Gets the current client session. /// Gets the current client session.
@@ -75,10 +74,14 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public MqttTopicFilter TopicFilter { get; set; } public MqttTopicFilter TopicFilter { get; set; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets or sets the user properties. /// Gets or sets the user properties.
/// <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; }
}
} }
@@ -2,20 +2,18 @@
// 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(string clientId, string userName, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties, CancellationToken cancellationToken)
{
public InterceptingUnsubscriptionEventArgs(CancellationToken cancellationToken, string clientId, IDictionary sessionItems, string topic, List<MqttUserProperty> userProperties)
{ {
CancellationToken = cancellationToken; CancellationToken = cancellationToken;
ClientId = clientId; ClientId = clientId;
UserName = userName;
SessionItems = sessionItems; SessionItems = sessionItems;
Topic = topic; Topic = topic;
UserProperties = userProperties; UserProperties = userProperties;
@@ -32,6 +30,11 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary> /// <summary>
/// Gets or sets whether the broker should close the client connection. /// Gets or sets whether the broker should close the client connection.
/// </summary> /// </summary>
@@ -68,5 +71,4 @@ namespace MQTTnet.Server
/// <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; }
}
} }
@@ -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,15 +2,13 @@
// 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.
@@ -47,5 +45,4 @@ namespace MQTTnet.Server
// </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,13 +2,12 @@
// 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)); ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
@@ -18,5 +17,4 @@ namespace MQTTnet.Server
public MqttPacket Packet { get; } public MqttPacket Packet { get; }
public string ReceiverClientId { get; } public string ReceiverClientId { get; }
}
} }
@@ -2,13 +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.
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)); ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
@@ -21,5 +18,4 @@ namespace MQTTnet.Server
public string ClientId { get; } public string ClientId { get; }
public List<MqttApplicationMessage> StoredRetainedMessages { get; } public List<MqttApplicationMessage> StoredRetainedMessages { get; }
}
} }

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