Compare commits

..
35 Commits
Author SHA1 Message Date
christian 14463d1ef9 Merge remote-tracking branch 'origin/master' 2026-06-21 16:24:00 +02:00
christian eeb0f7a261 Update release notes 2026-06-21 16:23:56 +02:00
Suhas Holla Karkada Chandrashekar 52c8f00ef8 Fix keep alive ping send timeout disconnect handling (#2253)
Fix keep alive ping send timeout disconnect
2026-06-21 16:19:45 +02:00
christian bef2ec6fba Update version 2026-06-20 11:02:42 +02:00
Christian d8f06280d5 Migrate to SLNX (#2252) 2026-06-20 11:00:27 +02:00
christian cf68e5e8d5 Update release notes 2026-06-18 17:55:07 +02:00
Alexander KöpkeandCopilot 68da92fd4b Add support for pluggable stream provider to enable SOCKS5 proxies (#2251)
* Add SOCKS5 proxy support via pluggable stream provider

Introduces MQTTnet.Extensions.Socks5, enabling SOCKS5 proxy support for MQTTnet TCP transport. Adds IMqttClientStreamProvider interface, Socks5StreamProvider implementation, and related configuration options. Updates MqttTcpChannel to support stream providers and TLS layering. Includes unit tests with a fake SOCKS5 server and usage samples. Solution and project references updated accordingly.

* Add SOCKS5 options validation for invalid configurations

* Tighten SOCKS5 RFC compliance checks

---------

Co-authored-by: Copilot <Copilot@users.noreply.github.com>
2026-06-18 17:51:52 +02:00
c1b46b0ae7 Client: Tolerate late acknowledgement packets (#2078, #2079) (#2247)
When a publish, subscribe or unsubscribe request was cancelled or
timed out client-side, the corresponding awaitable was removed from
the packet dispatcher. A subsequent PUBACK / PUBCOMP / SUBACK /
UNSUBACK arriving from the broker then fell through to the default
case and raised an MqttProtocolViolationException, which forced a
disconnect. Under high publish load (issue #2079) and from multiple
threads (issue #2078) this manifested as sporadic connection drops.

Match these acknowledgement packets explicitly and log a warning
when no awaiter is registered, instead of treating it as a protocol
violation. The connection is now preserved.

Also documents the thread-safety guarantees of IMqttClient: publish,
subscribe, unsubscribe and ping operations are safe to invoke
concurrently. Connect / disconnect must still be coordinated.

Co-authored-by: meisa <meisa@MAFDEL>
Co-authored-by: christian <6939810+chkr1011@users.noreply.github.com>
2026-06-16 20:28:40 +02:00
christian 745d1d25fe Update release notes 2026-06-13 18:07:57 +02:00
christian 905f070c37 Reset release notes 2026-06-13 18:06:14 +02:00
Suhas Holla Karkada Chandrashekar bca2ded8eb Fix MQTT v5 topic alias forwarding (#2250) 2026-06-13 18:04:42 +02:00
christian 8321806acf Fix nuget package generation 2026-01-25 11:50:48 +01:00
christian 081ef3077a Trigger actions 2026-01-22 18:18:13 +01:00
christian a3fbb928d6 Improve CI 2025-12-21 16:46:53 +01:00
christian 42ba46b885 Update release notes 2025-12-20 10:26:19 +01:00
Alexander Köpke 07409e3a35 Allow user properties as pre-encoded UTF-8 binary buffer (#2228)
* Add support for binary user properties in MQTT 5.0

* Optimize byte array reversal in SubscriptionTopicHashTests

* Use InvariantCulture for topic string conversion in Load_Tests

* switched to `Microsoft.Testing.Platform`

* Refactor CI workflow to separate signed and unsigned package builds

* Add condition to sign job to prevent execution on external pull requests

* Remove fallback to value as string in MqttUserProperty

* Refactor MqttUserProperty handling to use byte arrays for improved performance and add deprecation warnings for legacy methods
2025-12-18 17:46:35 +01:00
Christian 1a3e97afec Add support for dotnet 10 (#2229)
* Add dotnet10 as target framework and fix breaking changes

* Fix tests

* Fix tests for CI

* Fix typo
2025-12-07 16:24:34 +01: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
423 changed files with 25186 additions and 23837 deletions
+22 -13
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request]
env:
VERSION: "5.0.1.${{github.run_number}}"
VERSION: "5.2.0.${{github.run_number}}"
PACKAGE_SUFFIX: ""
jobs:
@@ -17,12 +17,26 @@ jobs:
with:
dotnet-version: |
8.0.x
10.0.x
- name: Checkout code
uses: actions/checkout@v4
- 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 }}
- name: Load strong name certificate
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
run: |
echo "$SNC_BASE64" | base64 --decode > "${{ github.workspace }}/certificate.snk"
shell: bash
env:
SNC_BASE64: ${{ secrets.SNC_BASE64 }}
- name: Build package (signed)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
run: dotnet build MQTTnet.slnx --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: Build package (unsigned)
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
run: dotnet build MQTTnet.slnx --configuration Release /p:FileVersion=${{ env.VERSION }} /p:AssemblyVersion=${{ env.VERSION }} /p:PackageVersion=${{ env.VERSION }}${{ env.PACKAGE_SUFFIX }}
- name: Upload nuget packages
uses: actions/upload-artifact@v4
@@ -41,24 +55,19 @@ jobs:
with:
dotnet-version: |
8.0.x
10.0.x
- name: Checkout code
uses: actions/checkout@v4
- name: Execute tests
run: dotnet test --framework net8.0 Source/MQTTnet.Tests/MQTTnet.Tests.csproj
run: dotnet test --framework net10.0 --configuration Release --project Source/MQTTnet.Tests/MQTTnet.Tests.csproj
sign:
needs: build
runs-on: windows-latest # Code signing must run on a Windows agent for Authenticode signing (dll/exe)
if: github.repository == 'dotnet/MQTTnet'
if: github.repository == 'dotnet/MQTTnet' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
steps:
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
- name: Download nuget packages
uses: actions/download-artifact@v4
with:
@@ -66,7 +75,7 @@ jobs:
path: nugets
- name: Install sign CLI tool
run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23127.3
run: dotnet tool install --tool-path . sign --version 0.9.1-beta.25379.1
- name: Sign nugets
shell: pwsh
@@ -107,7 +116,7 @@ jobs:
run: dotnet nuget push ${{ github.workspace }}/nugets/**/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
publish-nuget:
# Tag schema is 'v5.0.0-rc' etc.
# Tag schema is 'v5.1.0-rc' etc.
if: startsWith(github.ref, 'refs/tags/v')
needs: [sign, test]
runs-on: ubuntu-latest
+34
View File
@@ -0,0 +1,34 @@
<Project>
<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<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>
<Nullable>disable</Nullable>
<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>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)'=='true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
</Project>
+24
View File
@@ -0,0 +1,24 @@
<Project>
<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>
-90
View File
@@ -1,90 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet", "Source\MQTTnet\MQTTnet.csproj", "{3587E506-55A2-4EB3-99C7-DC01E42D25D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B3F60ECB-45BA-4C66-8903-8BB89CA67998}"
ProjectSection(SolutionItems) = preProject
CODE-OF-CONDUCT.md = CODE-OF-CONDUCT.md
LICENSE = LICENSE
README.md = README.md
Source\ReleaseNotes.md = Source\ReleaseNotes.md
.github\workflows\ci.yml = .github\workflows\ci.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspNetCore", "Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj", "{F10C4060-F7EE-4A83-919F-FF723E72F94A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.Rpc", "Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj", "{C444E9C8-95FA-430E-9126-274129DE16CD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Samples", "Samples\MQTTnet.Samples.csproj", "{71CF35F5-3327-4A91-AAF4-5340F6701771}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Tests", "Source\MQTTnet.Tests\MQTTnet.Tests.csproj", "{B270F32A-9F3E-42EE-A989-813E35E29ADB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Benchmarks", "Source\MQTTnet.Benchmarks\MQTTnet.Benchmarks.csproj", "{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.TestApp", "Source\MQTTnet.TestApp\MQTTnet.TestApp.csproj", "{175D5340-CC5B-4542-939D-4E7D15A0BC8D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.AspTestApp", "Source\MQTTnet.AspTestApp\MQTTnet.AspTestApp.csproj", "{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MQTTnet.Extensions.TopicTemplate", "Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj", "{374D5861-F7A6-42A2-8CBE-040846000EF9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MQTTnet.Server", "Source\MQTTnet.Server\MQTTnet.Server.csproj", "{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3587E506-55A2-4EB3-99C7-DC01E42D25D2}.Release|Any CPU.Build.0 = Release|Any CPU
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F10C4060-F7EE-4A83-919F-FF723E72F94A}.Release|Any CPU.Build.0 = Release|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C444E9C8-95FA-430E-9126-274129DE16CD}.Release|Any CPU.Build.0 = Release|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71CF35F5-3327-4A91-AAF4-5340F6701771}.Release|Any CPU.Build.0 = Release|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B270F32A-9F3E-42EE-A989-813E35E29ADB}.Release|Any CPU.Build.0 = Release|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F516E76-AAC4-4219-B7D1-34CDD3CFF381}.Release|Any CPU.Build.0 = Release|Any CPU
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{175D5340-CC5B-4542-939D-4E7D15A0BC8D}.Release|Any CPU.Build.0 = Release|Any CPU
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{72867E4C-4E15-4E8E-8FAB-AE9253286BBC}.Release|Any CPU.Build.0 = Release|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{374D5861-F7A6-42A2-8CBE-040846000EF9}.Release|Any CPU.Build.0 = Release|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C876EFFD-C5AD-4E26-BD9F-4C48252C02BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {07536672-5CBC-4BE3-ACE0-708A431A7894}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
EndGlobalSection
EndGlobal
+28
View File
@@ -0,0 +1,28 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
<Platform Name="x86" />
</Configurations>
<Folder Name="/Solution Items/">
<File Path=".github/workflows/ci.yml" />
<File Path="CODE-OF-CONDUCT.md" />
<File Path="Directory.Build.props" />
<File Path="Directory.Build.targets" />
<File Path="LICENSE" />
<File Path="README.md" />
<File Path="Source/ReleaseNotes.md" />
</Folder>
<Project Path="Samples/MQTTnet.Samples.csproj" />
<Project Path="Source/MQTTnet.AspnetCore/MQTTnet.AspNetCore.csproj" />
<Project Path="Source/MQTTnet.AspTestApp/MQTTnet.AspTestApp.csproj" />
<Project Path="Source/MQTTnet.Benchmarks/MQTTnet.Benchmarks.csproj" />
<Project Path="Source/MQTTnet.Extensions.Rpc/MQTTnet.Extensions.Rpc.csproj" />
<Project Path="Source/MQTTnet.Extensions.Socks5/MQTTnet.Extensions.Socks5.csproj" />
<Project Path="Source/MQTTnet.Extensions.TopicTemplate/MQTTnet.Extensions.TopicTemplate.csproj" />
<Project Path="Source/MQTTnet.Server/MQTTnet.Server.csproj" />
<Project Path="Source/MQTTnet.TestApp/MQTTnet.TestApp.csproj" />
<Project Path="Source/MQTTnet.Tests.Extensions.Socks5/MQTTnet.Tests.Extensions.Socks5.csproj" />
<Project Path="Source/MQTTnet.Tests/MQTTnet.Tests.csproj" />
<Project Path="Source/MQTTnet/MQTTnet.csproj" />
</Solution>
@@ -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">
<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_FOREACH/@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/>
</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)
[![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)
[![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)
+2 -2
View File
@@ -413,7 +413,7 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
if (e.ClientWasConnected)
{
// Use the current options as the new options.
await mqttClient.ConnectAsync(mqttClient.Options);
await mqttClient.ConnectAsync(mqttClientOptions);
}
};
@@ -490,7 +490,7 @@ m/XriWr/Cq4h/JfB7NTsezVslgkBaoU=
}
}
class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
sealed class SampleClientKerberosAuthenticationHandler : IMqttEnhancedAuthenticationHandler
{
public async Task HandleEnhancedAuthenticationAsync(MqttEnhancedAuthenticationEventArgs eventArgs)
{
+54 -38
View File
@@ -6,6 +6,9 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System;
using System.Text;
namespace MQTTnet.Samples.Client;
public static class Client_Publish_Samples
@@ -23,25 +26,23 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
Console.WriteLine("MQTT application message is published.");
}
public static async Task Publish_Multiple_Application_Messages()
@@ -54,38 +55,53 @@ public static class Client_Publish_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("19.5")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("20.0")
.Build();
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("20.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("21.0")
.Build();
applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic("samples/temperature/living_room")
.WithPayload("21.0")
.Build();
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.PublishAsync(applicationMessage, CancellationToken.None);
await mqttClient.DisconnectAsync();
await mqttClient.DisconnectAsync();
Console.WriteLine("MQTT application message is published.");
}
Console.WriteLine("MQTT application message is published.");
}
public static MqttApplicationMessage Create_Message_With_Binary_User_Property()
{
/*
* MQTT v5 user properties are encoded as UTF-8 strings. When the UTF-8 payload is already available
* as a byte buffer, the builder APIs can avoid creating intermediate strings.
*/
var encodedValue = Encoding.UTF8.GetBytes("sensor-01");
return new MqttApplicationMessageBuilder()
.WithTopic("samples/metadata/binary")
.WithUserProperty("client-id", encodedValue.AsMemory())
.WithUserProperty("checksum", new ArraySegment<byte>(encodedValue))
.WithPayload("metadata")
.Build();
}
}
+86
View File
@@ -0,0 +1,86 @@
// 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.
// ReSharper disable UnusedType.Global
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using MQTTnet.Extensions.Socks5;
using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client;
public static class Client_Socks5_Samples
{
public static async Task Connect_Tcp_Via_Socks5_No_Auth()
{
/*
* This sample connects to an MQTT broker through a SOCKS5 proxy that does not
* require authentication. The SOCKS5 stream provider performs the proxy handshake
* (RFC 1928) and returns a connected stream that the MQTT TCP channel can use as if
* it were a direct TCP connection.
*
* Host name resolution is performed by the proxy by default (ATYP=DOMAINNAME), which
* is the desired behavior when the MQTT broker hostname is only reachable from inside
* the network the proxy lives in.
*/
var mqttFactory = new MqttClientFactory();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.internal.example", 1883)
.WithSocks5Proxy(socks => socks
.WithHost("proxy.example.com")
.WithPort(1080))
.Build();
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected through SOCKS5.");
response.DumpToConsole();
var disconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
await mqttClient.DisconnectAsync(disconnectOptions, CancellationToken.None);
}
public static async Task Connect_Tcp_With_Tls_Via_Socks5_With_Credentials()
{
/*
* This sample connects to a TLS-protected MQTT broker through a SOCKS5 proxy that
* requires username/password authentication (RFC 1929).
*
* Note that the TLS handshake is layered on top of the SOCKS5 tunnel: the SNI used
* by SslStream is derived from the broker hostname (or the value passed to
* WithTargetHost), not from the proxy hostname.
*/
var mqttFactory = new MqttClientFactory();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.internal.example", 8883)
.WithTlsOptions(tls => tls
.WithTargetHost("broker.internal.example"))
.WithSocks5Proxy(socks => socks
.WithHost("proxy.example.com")
.WithPort(1080)
.WithCredentials("alice", "s3cr3t")
.WithResolveDnsRemotely(true)
.WithHandshakeTimeout(TimeSpan.FromSeconds(10)))
.Build();
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected through SOCKS5 with TLS.");
response.DumpToConsole();
var disconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder().Build();
await mqttClient.DisconnectAsync(disconnectOptions, CancellationToken.None);
}
}
+64 -71
View File
@@ -7,7 +7,7 @@
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
using MQTTnet.Extensions.TopicTemplate;
using System.Text;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers;
@@ -16,8 +16,6 @@ namespace MQTTnet.Samples.Client;
public static class Client_Subscribe_Samples
{
static readonly MqttTopicTemplate sampleTemplate = new("mqttnet/samples/topic/{id}");
public static async Task Handle_Received_Application_Message()
{
/*
@@ -26,72 +24,68 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
// Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all
// received messages get lost.
mqttClient.ApplicationMessageReceivedAsync += e =>
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
Console.WriteLine("Received application message.");
e.DumpToConsole();
// Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all
// received messages get lost.
mqttClient.ApplicationMessageReceivedAsync += e =>
{
Console.WriteLine("Received application message.");
e.DumpToConsole();
return Task.CompletedTask;
};
return Task.CompletedTask;
};
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("mqttnet/samples/topic/2").Build();
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "2")).Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
Console.WriteLine("MQTT client subscribed to topic.");
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
Console.WriteLine("Press enter to exit.");
Console.ReadLine();
}
public static async Task Send_Responses()
{
/*
* 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();
using (var mqttClient = mqttFactory.CreateMqttClient())
using var mqttClient = mqttFactory.CreateMqttClient();
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
{
mqttClient.ApplicationMessageReceivedAsync += delegate(MqttApplicationMessageReceivedEventArgs args)
{
// Do some work with the message...
// Do some work with the message...
// Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!";
// Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", Encoding.UTF8.GetBytes("Data")));
// Now the broker will resend the message again.
return Task.CompletedTask;
};
// Now the broker will resend the message again.
return Task.CompletedTask;
};
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicTemplate(sampleTemplate.WithParameter("id", "1")).Build();
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter("topic/test").Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
public static async Task Subscribe_Multiple_Topics()
@@ -102,27 +96,26 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Create the subscribe options including several topics with different options.
// 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();
// 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()
.WithTopicFilter(t => t.WithTopic("topic/1").WithAtLeastOnceQoS())
.WithTopicFilter(t => t.WithTopic("topic/2").WithAtMostOnceQoS())
.WithTopicFilter(t => t.WithTopic("topic/3").WithNoLocal())
.WithTopicFilter(t => t.WithRetainHandling(MqttRetainHandling.SendAtSubscribe))
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics.");
Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
public static async Task Subscribe_Topic()
@@ -133,24 +126,24 @@ public static class Client_Subscribe_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
using var mqttClient = mqttFactory.CreateMqttClient();
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 response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder().WithTopicFilter(topicFilter).Build();
Console.WriteLine("MQTT client subscribed to topic.");
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
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
@@ -178,7 +171,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:
@@ -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();
}
}
+15 -18
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Globalization;
using System.Text;
using MQTTnet.Diagnostics.Logger;
@@ -26,17 +27,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
using var mqttClient = mqttFactory.CreateMqttClient();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected.");
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
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.
var mqttEventLogger = new MqttNetEventLogger("MyCustomLogger");
mqttEventLogger.LogMessagePublished += (sender, args) =>
mqttEventLogger.LogMessagePublished += (_, args) =>
{
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)
{
output.AppendLine(args.LogMessage.Exception.ToString());
@@ -68,17 +67,15 @@ public static class Logger_Samples
.WithTcpServer("broker.hivemq.com")
.Build();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
using var mqttClient = mqttFactory.CreateMqttClient();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected.");
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
sealed class MyLogger : IMqttNetLogger
@@ -20,23 +20,21 @@ public static class PackageInspection_Samples
var mqttFactory = new MqttClientFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = mqttFactory.CreateClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
mqttClient.InspectPacketAsync += OnInspectPacket;
mqttClient.InspectPacketAsync += OnInspectPacket;
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
Console.WriteLine("MQTT client is connected.");
Console.WriteLine("MQTT client is connected.");
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
var mqttClientDisconnectOptions = mqttFactory.CreateClientDisconnectOptionsBuilder()
.Build();
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
await mqttClient.DisconnectAsync(mqttClientDisconnectOptions, CancellationToken.None);
}
static Task OnInspectPacket(InspectMqttPacketEventArgs eventArgs)
+7 -5
View File
@@ -8,17 +8,19 @@ namespace MQTTnet.Samples.Helpers;
internal static class ObjectExtensions
{
static readonly JsonSerializerOptions SerializerOptions = new()
{
WriteIndented = true
};
public static TObject DumpToConsole<TObject>(this TObject @object)
{
var output = "NULL";
if (@object != null)
{
output = JsonSerializer.Serialize(@object, new JsonSerializerOptions
{
WriteIndented = true
});
output = JsonSerializer.Serialize(@object, SerializerOptions);
}
Console.WriteLine($"[{@object?.GetType().Name}]:\r\n{output}");
return @object;
}
+2 -10
View File
@@ -2,25 +2,17 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<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>
<NoWarn>$(NoWarn);CA1707</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
<ProjectReference Include="..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj"/>
<ProjectReference Include="..\Source\MQTTnet.Extensions.Socks5\MQTTnet.Extensions.Socks5.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Extensions.TopicTemplate\MQTTnet.Extensions.TopicTemplate.csproj" />
<ProjectReference Include="..\Source\MQTTnet.Server\MQTTnet.Server.csproj"/>
<ProjectReference Include="..\Source\MQTTnet\MQTTnet.csproj"/>
+4 -3
View File
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Reflection;
Console.BackgroundColor = ConsoleColor.White;
@@ -28,7 +29,7 @@ Console.Write("Please choose sample class (press Enter to continue): ");
Console.ResetColor();
var input = Console.ReadLine();
var selectedIndex = int.Parse(input ?? "0");
var selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleClass = sampleClasses[selectedIndex];
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();
input = Console.ReadLine();
selectedIndex = int.Parse(input ?? "0");
selectedIndex = int.Parse(input ?? "0", CultureInfo.InvariantCulture);
var selectedSampleMethod = sampleMethods[selectedIndex];
Console.WriteLine();
@@ -58,7 +59,7 @@ try
{
if (selectedSampleMethod.Invoke(null, null) is Task task)
{
await task;
await task;
}
}
catch (Exception exception)
+13 -15
View File
@@ -26,23 +26,21 @@ public static class RpcClient_Samples
// The RPC client is an addon for the existing client. So we need a regular client
// which is wrapped later.
using (var mqttClient = mqttFactory.CreateMqttClient())
using var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions);
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions);
using (var mqttRpcClient = mqttFactory.CreateMqttRpcClient(mqttClient))
{
// Access to a fully featured application message is not supported for RPC calls!
// The method will throw an exception when the response was not received in time.
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
Console.WriteLine("The RPC call was successful.");
// Access to a fully featured application message is not supported for RPC calls!
// The method will throw an exception when the response was not received in time.
await mqttRpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
}
Console.WriteLine("The RPC call was successful.");
}
/*
+7 -4
View File
@@ -8,6 +8,7 @@
// ReSharper disable EmptyConstructor
// ReSharper disable MemberCanBeMadeStatic.Local
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@@ -52,14 +53,14 @@ public static class Server_ASP_NET_Samples
// Inject other services via constructor.
}
public Task OnClientConnected(ClientConnectedEventArgs eventArgs)
public static Task OnClientConnected(ClientConnectedEventArgs eventArgs)
{
Console.WriteLine($"Client '{eventArgs.ClientId}' connected.");
return Task.CompletedTask;
}
public Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
public static Task ValidateConnection(ValidatingConnectionEventArgs eventArgs)
{
Console.WriteLine($"Client '{eventArgs.ClientId}' wants to connect. Accepting!");
return Task.CompletedTask;
@@ -68,6 +69,7 @@ public static class Server_ASP_NET_Samples
sealed class Startup
{
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment, MqttController mqttController)
{
app.UseRouting();
@@ -88,11 +90,12 @@ public static class Server_ASP_NET_Samples
* Attach event handlers etc. if required.
*/
server.ValidatingConnectionAsync += mqttController.ValidateConnection;
server.ClientConnectedAsync += mqttController.OnClientConnected;
server.ValidatingConnectionAsync += MqttController.ValidateConnection;
server.ClientConnectedAsync += MqttController.OnClientConnected;
});
}
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedMqttServer(
+15 -17
View File
@@ -25,28 +25,26 @@ public static class Server_Diagnostics_Samples
var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
// Attach the event handler.
mqttServer.ClientAcknowledgedPublishPacketAsync += e =>
{
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
Console.WriteLine($"Client '{e.ClientId}' acknowledged packet {e.PublishPacket.PacketIdentifier} with topic '{e.PublishPacket.Topic}'");
// It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
// It is also possible to read additional data from the client response. This requires casting the response packet.
var qos1AcknowledgePacket = e.AcknowledgePacket as MqttPubAckPacket;
Console.WriteLine($"QoS 1 reason code: {qos1AcknowledgePacket?.ReasonCode}");
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos1AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance;
};
var qos2AcknowledgePacket = e.AcknowledgePacket as MqttPubCompPacket;
Console.WriteLine($"QoS 2 reason code: {qos2AcknowledgePacket?.ReasonCode}");
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
await mqttServer.StopAsync();
}
}
+40 -13
View File
@@ -9,6 +9,7 @@
// ReSharper disable MemberCanBeMadeStatic.Local
using MQTTnet.Internal;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Samples.Server;
@@ -25,23 +26,49 @@ public static class Server_Intercepting_Samples
var mqttServerFactory = new MqttServerFactory();
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
mqttServer.InterceptingPublishAsync += args =>
{
mqttServer.InterceptingPublishAsync += args =>
// Here we only change the topic of the received application message.
// but also changing the payload etc. is required. Changing the QoS after
// transmitting is not supported and makes no sense at all.
args.ApplicationMessage.Topic += "/manipulated";
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
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))
{
// Here we only change the topic of the received application message.
// but also changing the payload etc. is required. Changing the QoS after
// transmitting is not supported and makes no sense at all.
args.ApplicationMessage.Topic += "/manipulated";
args.Response.ReasonCode = MqttSubscribeReasonCode.NotAuthorized;
}
return CompletedTask.Instance;
};
return CompletedTask.Instance;
};
await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
}
@@ -29,64 +29,62 @@ public static class Server_Retained_Messages_Samples
// Due to security reasons the "default" endpoint (which is unencrypted) is not enabled by default!
var mqttServerOptions = mqttServerFactory.CreateServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var server = mqttServerFactory.CreateMqttServer(mqttServerOptions))
using var server = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs =>
{
// Make sure that the server will load the retained messages.
server.LoadingRetainedMessageAsync += async eventArgs =>
try
{
try
{
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
var models = await JsonSerializer.DeserializeAsync<List<MqttRetainedMessageModel>>(File.OpenRead(storePath)) ?? new List<MqttRetainedMessageModel>();
var retainedMessages = models.Select(m => m.ToApplicationMessage()).ToList();
eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
{
// Ignore because nothing is stored yet.
Console.WriteLine("No retained messages stored yet.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to persist the changed retained messages.
server.RetainedMessageChangedAsync += async eventArgs =>
eventArgs.LoadedRetainedMessages = retainedMessages;
Console.WriteLine("Retained messages loaded.");
}
catch (FileNotFoundException)
{
try
{
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to clear the retained messages when they are all deleted via API.
server.RetainedMessagesClearedAsync += _ =>
// Ignore because nothing is stored yet.
Console.WriteLine("No retained messages stored yet.");
}
catch (Exception exception)
{
File.Delete(storePath);
return Task.CompletedTask;
};
Console.WriteLine(exception);
}
};
await server.StartAsync();
// Make sure to persist the changed retained messages.
server.RetainedMessageChangedAsync += async eventArgs =>
{
try
{
// This sample uses the property _StoredRetainedMessages_ which will contain all(!) retained messages.
// The event args also contain the affected retained message (property ChangedRetainedMessage). This can be
// used to write all retained messages to dedicated files etc. Then all files must be loaded and a full list
// of retained messages must be provided in the loaded event.
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
var models = eventArgs.StoredRetainedMessages.Select(MqttRetainedMessageModel.Create);
var buffer = JsonSerializer.SerializeToUtf8Bytes(models);
await File.WriteAllBytesAsync(storePath, buffer);
Console.WriteLine("Retained messages saved.");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
};
// Make sure to clear the retained messages when they are all deleted via API.
server.RetainedMessagesClearedAsync += _ =>
{
File.Delete(storePath);
return Task.CompletedTask;
};
await server.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
}
sealed class MqttRetainedMessageModel
+53 -62
View File
@@ -6,6 +6,7 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable InconsistentNaming
using System.Globalization;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Protocol;
using MQTTnet.Server;
@@ -22,17 +23,15 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information.
*/
using (var mqttServer = await StartMqttServer())
{
// Let the client connect.
await Task.Delay(TimeSpan.FromSeconds(5));
using var mqttServer = await StartMqttServer();
// Let the client connect.
await Task.Delay(TimeSpan.FromSeconds(5));
// Now disconnect the client (if connected).
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
if (affectedClient != null)
{
await affectedClient.DisconnectAsync();
}
// Now disconnect the client (if connected).
var affectedClient = (await mqttServer.GetClientsAsync()).FirstOrDefault(c => c.Id == "MyClient");
if (affectedClient != null)
{
await affectedClient.DisconnectAsync();
}
}
@@ -44,18 +43,16 @@ public static class Server_Simple_Samples
* See _Run_Minimal_Server_ for more information.
*/
using (var mqttServer = await StartMqttServer())
{
// Create a new message using the builder as usual.
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
using var mqttServer = await StartMqttServer();
// Create a new message using the builder as usual.
var message = new MqttApplicationMessageBuilder().WithTopic("HelloWorld").WithPayload("Test").Build();
// Now inject the new message at the broker.
await mqttServer.InjectApplicationMessage(
new InjectedMqttApplicationMessage(message)
{
SenderClientId = "SenderClientId"
});
}
// Now inject the new message at the broker.
await mqttServer.InjectApplicationMessage(
new InjectedMqttApplicationMessage(message)
{
SenderClientId = "SenderClientId"
});
}
public static async Task Run_Minimal_Server()
@@ -77,16 +74,14 @@ public static class Server_Simple_Samples
// .WithDefaultEndpointPort(1234)
// .Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
public static async Task Run_Server_With_Logging()
@@ -103,16 +98,14 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
public static async Task Validating_Connections()
@@ -127,37 +120,35 @@ public static class Server_Simple_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
// Setup connection validation before starting the server so that there is
// no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e =>
{
// Setup connection validation before starting the server so that there is
// no change to connect without valid credentials.
mqttServer.ValidatingConnectionAsync += e =>
if (e.ClientId != "ValidClientId")
{
if (e.ClientId != "ValidClientId")
{
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
e.ReasonCode = MqttConnectReasonCode.ClientIdentifierNotValid;
}
if (e.UserName != "ValidUser")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
if (e.UserName != "ValidUser")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
if (e.Password != "SecretPassword")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
if (e.Password != "SecretPassword")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
return Task.CompletedTask;
};
return Task.CompletedTask;
};
await mqttServer.StartAsync();
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
await mqttServer.StopAsync();
}
await mqttServer.StopAsync();
}
static async Task<MqttServer> StartMqttServer()
@@ -171,7 +162,7 @@ public static class Server_Simple_Samples
return server;
}
class ConsoleLogger : IMqttNetLogger
sealed class ConsoleLogger : IMqttNetLogger
{
readonly object _consoleSyncRoot = new();
@@ -201,7 +192,7 @@ public static class Server_Simple_Samples
if (parameters?.Length > 0)
{
message = string.Format(message, parameters);
message = string.Format(CultureInfo.InvariantCulture, message, parameters);
}
lock (_consoleSyncRoot)
+20 -24
View File
@@ -31,16 +31,14 @@ public static class Server_TLS_Samples
var mqttServerOptions = new MqttServerOptionsBuilder().WithEncryptionCertificate(certificate).WithEncryptedEndpoint().Build();
using (var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions))
{
await mqttServer.StartAsync();
using var mqttServer = mqttServerFactory.CreateMqttServer(mqttServerOptions);
await mqttServer.StartAsync();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
Console.WriteLine("Press Enter to exit.");
Console.ReadLine();
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
// Stop and dispose the MQTT server if it is no longer needed!
await mqttServer.StopAsync();
}
static X509Certificate2 CreateSelfSignedCertificate(string oid)
@@ -50,26 +48,24 @@ public static class Server_TLS_Samples
sanBuilder.AddIpAddress(IPAddress.IPv6Loopback);
sanBuilder.AddDnsName("localhost");
using (var rsa = RSA.Create())
{
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
using var rsa = RSA.Create();
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new(oid) }, false));
certRequest.CertificateExtensions.Add(sanBuilder.Build());
certRequest.CertificateExtensions.Add(sanBuilder.Build());
using (var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10)))
{
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null!,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
using var certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now.AddMinutes(-10), DateTimeOffset.Now.AddMinutes(10));
#pragma warning disable SYSLIB0057
var pfxCertificate = new X509Certificate2(
certificate.Export(X509ContentType.Pfx),
(string)null!,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
#pragma warning restore SYSLIB0057
return pfxCertificate;
}
}
return pfxCertificate;
}
}
@@ -1,19 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<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>
<NoWarn>$(NoWarn);CA1707</NoWarn>
</PropertyGroup>
<ItemGroup>
+15 -13
View File
@@ -4,20 +4,22 @@
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;
public IndexModel(ILogger<IndexModel> logger)
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
_logger = logger;
}
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.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using MQTTnet.Server;
@@ -33,10 +32,8 @@ public static class ApplicationBuilderExtensions
}
var adapter = app.ApplicationServices.GetRequiredService<MqttWebSocketServerAdapter>();
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false))
{
await adapter.RunWebSocketConnectionAsync(webSocket, context);
}
using var webSocket = await context.WebSockets.AcceptWebSocketAsync(subProtocol).ConfigureAwait(false);
await adapter.RunWebSocketConnectionAsync(webSocket, context);
});
return app;
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Server;
namespace MQTTnet.AspNetCore;
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore;
@@ -4,13 +4,12 @@
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,23 +2,20 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.AspNetCore.Builder;
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)
ArgumentNullException.ThrowIfNull(endpoints);
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{
ArgumentNullException.ThrowIfNull(endpoints);
endpoints.MapConnectionHandler<MqttConnectionHandler>(pattern, options =>
{
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
}
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
}
}
}
@@ -1,60 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.AspNetCore</AssemblyName>
<RootNamespace>MQTTnet.AspNetCore</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<IsAotCompatible>true</IsAotCompatible>
<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>
<Authors>The contributors of MQTTnet</Authors>
<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>
<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>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<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>
<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"/>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release'">
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet.Server\MQTTnet.Server.csproj"/>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
@@ -2,33 +2,31 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
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)
ArgumentNullException.ThrowIfNull(options);
switch (options.ChannelOptions)
{
if (options == null) throw new ArgumentNullException(nameof(options));
switch (options.ChannelOptions)
case MqttClientTcpOptions tcpOptions:
{
case MqttClientTcpOptions tcpOptions:
{
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
var tcpConnection = new SocketConnection(tcpOptions.RemoteEndpoint);
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection);
}
default:
{
throw new NotSupportedException();
}
var formatter = new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535));
return new MqttConnectionContext(formatter, tcpConnection);
}
default:
{
throw new NotSupportedException();
}
}
}
}
}
@@ -2,13 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections.Features;
using Microsoft.AspNetCore.Http.Features;
@@ -61,27 +58,6 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
}
}
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 bool IsSecureConnection
{
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 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)
{
if (_connection is SocketConnection tcp && !tcp.IsConnected)
@@ -232,8 +250,8 @@ public sealed class MqttConnectionContext : IMqttChannelAdapter
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
int offset = buffer.Packet.Count;
buffer.Payload.CopyTo(destination: span.Slice(offset));
var offset = buffer.Packet.Count;
buffer.Payload.CopyTo(span.Slice(offset));
output.Advance(buffer.Length);
}
}
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Connections.Features;
using MQTTnet.Adapter;
@@ -33,13 +31,11 @@ public sealed class MqttConnectionHandler : ConnectionHandler, IMqttServerAdapte
}
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(_serverOptions.WriterBufferSize, _serverOptions.WriterBufferSizeMax));
using (var adapter = new MqttConnectionContext(formatter, connection))
using var adapter = new MqttConnectionContext(formatter, connection);
var clientHandler = ClientHandler;
if (clientHandler != null)
{
var clientHandler = ClientHandler;
if (clientHandler != null)
{
await clientHandler(adapter).ConfigureAwait(false);
}
await clientHandler(adapter).ConfigureAwait(false);
}
}
@@ -2,10 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Server;
@@ -2,9 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;
namespace MQTTnet.AspNetCore;
@@ -29,6 +26,6 @@ public static class MqttSubProtocolSelector
ArgumentNullException.ThrowIfNull(requestedSubProtocolValues);
// Order the protocols to also match "mqtt", "mqttv-3.1", "mqttv-3.11" etc.
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLower().StartsWith("mqtt"));
return requestedSubProtocolValues.OrderByDescending(p => p.Length).FirstOrDefault(p => p.ToLowerInvariant().StartsWith("mqtt", StringComparison.InvariantCulture));
}
}
@@ -2,10 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Net;
using System.Net.WebSockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
@@ -43,10 +41,8 @@ public sealed class MqttWebSocketServerAdapter : IMqttServerAdapter
var formatter = new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535));
var channel = new MqttWebSocketChannel(webSocket, remoteEndPoint, isSecureConnection, clientCertificate);
using (var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger))
{
await clientHandler(channelAdapter).ConfigureAwait(false);
}
using var channelAdapter = new MqttChannelAdapter(channel, formatter, _logger);
await clientHandler(channelAdapter).ConfigureAwait(false);
}
}
finally
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using MQTTnet.Adapter;
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
+7 -16
View File
@@ -2,44 +2,35 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
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;
Action _callback;
SocketError _error;
public SocketAwaitable(PipeScheduler ioScheduler)
{
_ioScheduler = ioScheduler;
}
public bool IsCompleted => ReferenceEquals(_callback, _callbackCompleted);
public bool IsCompleted => ReferenceEquals(_callback, CallbackCompleted);
public void Complete(int bytesTransferred, SocketError socketError)
{
_error = socketError;
_bytesTransferred = bytesTransferred;
var continuation = Interlocked.Exchange(ref _callback, _callbackCompleted);
var continuation = Interlocked.Exchange(ref _callback, CallbackCompleted);
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()
{
Debug.Assert(ReferenceEquals(_callback, _callbackCompleted));
Debug.Assert(ReferenceEquals(_callback, CallbackCompleted));
_callback = null;
@@ -64,7 +55,7 @@ public class SocketAwaitable : ICriticalNotifyCompletion
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);
}
+14 -12
View File
@@ -2,13 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using MQTTnet.Exceptions;
@@ -18,11 +14,11 @@ namespace MQTTnet.AspNetCore;
public sealed class SocketConnection : ConnectionContext
{
readonly EndPoint _endPoint;
volatile bool _aborted;
IDuplexPipe _application;
SocketReceiver _receiver;
SocketSender _sender;
Socket _socket;
public SocketConnection(EndPoint endPoint)
@@ -40,10 +36,13 @@ public sealed class SocketConnection : ConnectionContext
}
public override string ConnectionId { get; set; }
public override IFeatureCollection Features { get; }
public bool IsConnected { get; private set; }
public override IDictionary<object, object> Items { get; set; }
public override IDuplexPipe Transport { get; set; }
public override ValueTask DisposeAsync()
@@ -78,7 +77,7 @@ public sealed class SocketConnection : ConnectionContext
IsConnected = true;
}
Exception ConnectionAborted()
static MqttCommunicationException ConnectionAborted()
{
return new MqttCommunicationException("Connection Aborted");
}
@@ -95,8 +94,7 @@ public sealed class SocketConnection : ConnectionContext
{
error = new MqttCommunicationException(ex);
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted || ex.SocketErrorCode == SocketError.ConnectionAborted ||
ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.InvalidArgument)
catch (SocketException ex) when (ex.SocketErrorCode is SocketError.OperationAborted or SocketError.ConnectionAborted or SocketError.Interrupted or SocketError.InvalidArgument)
{
if (!_aborted)
{
@@ -123,10 +121,10 @@ public sealed class SocketConnection : ConnectionContext
{
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();
FlushResult result;
if (!flushTask.IsCompleted)
{
await flushTask;
result = await flushTask;
}
else
{
result = flushTask.GetAwaiter().GetResult();
}
var result = flushTask.GetAwaiter().GetResult();
if (result.IsCompleted)
{
// 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.
// See the LICENSE file in the project root for more information.
using System;
using System.IO.Pipelines;
using System.Net.Sockets;
namespace MQTTnet.AspNetCore;
public sealed class SocketReceiver
public sealed class SocketReceiver : IDisposable
{
readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new();
@@ -33,4 +32,9 @@ public sealed class SocketReceiver
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.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net.Sockets;
@@ -12,7 +10,7 @@ using System.Runtime.InteropServices;
namespace MQTTnet.AspNetCore;
public sealed class SocketSender
public sealed class SocketSender : IDisposable
{
readonly SocketAwaitable _awaitable;
readonly SocketAsyncEventArgs _eventArgs = new();
@@ -28,6 +26,11 @@ public sealed class SocketSender
_eventArgs.Completed += (_, e) => ((SocketAwaitable)e.UserToken).Complete(e.BytesTransferred, e.SocketError);
}
public void Dispose()
{
_eventArgs.Dispose();
}
public SocketAwaitable SendAsync(in ReadOnlySequence<byte> buffers)
{
if (buffers.IsSingleSegment)
@@ -0,0 +1 @@
[assembly: DoNotParallelize]
+42 -48
View File
@@ -2,64 +2,58 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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]
public async Task Synchronize_100_Tasks()
{
[Benchmark]
public async Task Synchronize_100_Tasks()
const int tasksCount = 100;
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{
const int tasksCount = 100;
var tasks = new Task[tasksCount];
var asyncLock = new AsyncLock();
var globalI = 0;
for (var i = 0; i < tasksCount; i++)
{
tasks[i] = Task.Run(
async () =>
{
using (await asyncLock.EnterAsync().ConfigureAwait(false))
{
var localI = globalI;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (globalI != tasksCount)
{
throw new Exception($"Code is broken ({globalI})!");
}
}
[Benchmark]
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using (var cancellationToken = new CancellationTokenSource())
{
for (var i = 0; i < 100000; i++)
tasks[i] = Task.Run(
async () =>
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
using (await asyncLock.EnterAsync().ConfigureAwait(false))
{
var localI = globalI;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
localI++;
globalI = localI;
}
}
});
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (globalI != tasksCount)
{
throw new InvalidOperationException($"Code is broken ({globalI})!");
}
}
[Benchmark]
public async Task Wait_100_000_Times()
{
var asyncLock = new AsyncLock();
using var cancellationToken = new CancellationTokenSource();
for (var i = 0; i < 100000; i++)
{
using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{
}
}
}
+3 -4
View File
@@ -2,9 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.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.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Threading;
using BenchmarkDotNet.Attributes;
using MQTTnet.Adapter;
using MQTTnet.Diagnostics.Logger;
@@ -12,79 +9,84 @@ using MQTTnet.Formatter;
using MQTTnet.Packets;
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;
int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
[Benchmark]
public void Receive_10000_Messages()
{
MqttChannelAdapter _channelAdapter;
int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream;
_stream.Position = 0;
[Benchmark]
public void Receive_10000_Messages()
for (var i = 0; i < 10000; i++)
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
_channelAdapter.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
[Benchmark]
public void Send_10000_Messages()
_stream.Position = 0;
}
[Benchmark]
public void Send_10000_Messages()
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{
_stream.Position = 0;
for (var i = 0; i < 10000; i++)
{
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
}
_stream.Position = 0;
_channelAdapter.SendPacketAsync(_packet, CancellationToken.None).GetAwaiter().GetResult();
}
[GlobalSetup]
public void Setup()
_stream.Position = 0;
}
[GlobalSetup]
public void Setup()
{
_packet = new MqttPublishPacket
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
Topic = "A"
};
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var serializedPacket = Join(serializer.Encode(_packet).Join());
var serializedPacket = Join(serializer.Encode(_packet).Join());
_iterations = 10000;
_iterations = 10000;
_stream = new MemoryStream(_iterations * serializedPacket.Length);
_stream = new MemoryStream(_iterations * serializedPacket.Length);
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
_stream.Position = 0;
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
for (var i = 0; i < _iterations; i++)
{
_stream.Write(serializedPacket, 0, serializedPacket.Length);
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
_stream.Position = 0;
return buffer.ToArray();
var channel = new MemoryMqttChannel(_stream);
_channelAdapter = new MqttChannelAdapter(channel, serializer, new MqttNetEventLogger());
}
static byte[] Join(params ArraySegment<byte>[] chunks)
{
var buffer = new MemoryStream();
foreach (var chunk in chunks)
{
buffer.Write(chunk.Array, chunk.Offset, chunk.Count);
}
return buffer.ToArray();
}
public void Dispose()
{
_channelAdapter?.Dispose();
_stream?.Dispose();
}
}
+75 -75
View File
@@ -2,90 +2,90 @@
// 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.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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;
MqttNetSourceLogger _sourceNullLogger;
MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger;
MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener;
bool _useHandler;
[GlobalSetup]
public void Setup()
{
MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger;
_nullLogger = new MqttNetNullLogger();
_sourceNullLogger = _nullLogger.WithSource("Source");
MqttNetEventLogger _eventLogger;
MqttNetSourceLogger _sourceEventLogger;
_eventLogger = new MqttNetEventLogger();
_eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
MqttNetEventLogger _eventLoggerNoListener;
MqttNetSourceLogger _sourceEventLoggerNoListener;
_eventLoggerNoListener = new MqttNetEventLogger();
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
}
bool _useHandler;
[GlobalSetup]
public void Setup()
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
{
if (_useHandler)
{
_nullLogger = new MqttNetNullLogger();
_sourceNullLogger = _nullLogger.WithSource("Source");
_eventLogger = new MqttNetEventLogger();
_eventLogger.LogMessagePublished += OnLogMessagePublished;
_sourceEventLogger = _eventLogger.WithSource("Source");
_eventLoggerNoListener = new MqttNetEventLogger();
_sourceEventLoggerNoListener = _eventLoggerNoListener.WithSource("Source");
}
void OnLogMessagePublished(object sender, MqttNetLogMessagePublishedEventArgs eventArgs)
{
if (_useHandler)
{
eventArgs.LogMessage.ToString();
}
}
[Benchmark]
public void Log_10000_Messages_No_Listener()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_To_String()
{
_useHandler = true;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_Without_To_String()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_NullLogger()
{
for (var i = 0; i < 10000; i++)
{
_sourceNullLogger.Verbose("test log message {0}", "parameter");
}
Debug.WriteLine(eventArgs.LogMessage.ToString());
}
}
}
[Benchmark]
public void Log_10000_Messages_No_Listener()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLoggerNoListener.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_To_String()
{
_useHandler = true;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_Without_To_String()
{
_useHandler = false;
for (var i = 0; i < 10000; i++)
{
_sourceEventLogger.Verbose("test log message {0}", "parameter");
}
}
[Benchmark]
public void Log_10000_Messages_With_NullLogger()
{
for (var i = 0; i < 10000; i++)
{
_sourceNullLogger.Verbose("test log message {0}", "parameter");
}
}
}
@@ -1,24 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<OutputType>Exe</OutputType>
<DebugType>Full</DebugType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138;NU1803;NU1901;NU1902;CS8892</NoWarn>
<NoWarn>$(NoWarn);CS8892;CA1707;CA1051;CA1822</NoWarn>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<NuGetAuditMode>all</NuGetAuditMode>
<NuGetAudit>true</NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
</ItemGroup>
<ItemGroup>
@@ -1,40 +1,39 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System;
namespace MQTTnet.Benchmarks
namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class MemoryCopyBenchmark
const int MaxLength = 1024 * 8;
byte[] _source;
byte[] _target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{
const int max_length = 1024 * 8;
private byte[] source;
private byte[] target;
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
public int Length { get; set; }
[GlobalSetup]
public void Setup()
{
source = new byte[max_length];
target = new byte[max_length];
}
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(source, 0, target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
}
_source = new byte[MaxLength];
_target = new byte[MaxLength];
}
[Benchmark(Baseline = true)]
public void Array_Copy()
{
Array.Copy(_source, 0, _target, 0, Length);
}
[Benchmark]
public void Memory_Copy()
{
Internal.MqttMemoryHelper.Copy(_source, 0, _target, 0, Length);
}
}
@@ -2,11 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using MQTTnet.Packets;
using MQTTnet.Server;
@@ -14,7 +9,7 @@ using MQTTnet.Server;
namespace MQTTnet.Benchmarks;
[MemoryDiagnoser]
public class MessageDeliveryBenchmark : BaseBenchmark
public sealed class MessageDeliveryBenchmark : BaseBenchmark, IDisposable
{
List<string> _allSubscribedTopics; // Keep track of the subset of topics that are subscribed
CancellationTokenSource _cancellationTokenSource;
@@ -23,22 +18,19 @@ public class MessageDeliveryBenchmark : BaseBenchmark
int _messagesExpectedCount;
int _messagesReceivedCount;
Dictionary<string, IMqttClient> _mqttPublisherClientsByPublisherName;
MqttServer _mqttServer;
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;
List<MqttApplicationMessage> _topicPublishMessages;
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]
public void Cleanup()
{
@@ -64,14 +56,11 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer = null;
}
/// <summary>
/// Publish messages and wait for messages sent to subscribers
/// </summary>
[Benchmark]
public void DeliverMessages()
{
// 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
@@ -82,8 +71,6 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// same payload for all messages
var payload = new byte[] { 1, 2, 3, 4 };
var tasks = new List<Task>();
// publish a message for each subscribed topic
foreach (var topic in _allSubscribedTopics)
{
@@ -101,35 +88,29 @@ public class MessageDeliveryBenchmark : BaseBenchmark
}
catch
{
// Ignore all errors.
}
_cancellationTokenSource.Dispose();
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]
public void Setup()
{
_lockMsgCount = new object();
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
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);
}
TopicGenerator.Generate(_numPublishers, _numTopicsPerPublisher, out _topicsByPublisher, out _, out _);
// Create server
var serverFactory = new MqttServerFactory();
@@ -139,7 +120,7 @@ public class MessageDeliveryBenchmark : BaseBenchmark
_mqttServer.StartAsync().GetAwaiter().GetResult();
// Create publisher clients
_mqttPublisherClientsByPublisherName = new Dictionary<string, IMqttClient>();
_mqttPublisherClientsByPublisherName = [];
foreach (var pt in _topicsByPublisher)
{
var publisherName = pt.Key;
@@ -151,13 +132,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Create subscriber clients
_mqttSubscriberClients = new List<IMqttClient>();
for (var i = 0; i < NumSubscribers; i++)
for (var i = 0; i < _numSubscribers; i++)
{
var mqttSubscriberClient = clientFactory.CreateMqttClient();
_mqttSubscriberClients.Add(mqttSubscriberClient);
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
lock (_lockMsgCount)
@@ -171,12 +152,13 @@ public class MessageDeliveryBenchmark : BaseBenchmark
return Task.CompletedTask;
};
mqttSubscriberClient.ConnectAsync(subscriberOptions).GetAwaiter().GetResult();
}
var allTopics = new List<string>();
_publisherByTopic = new Dictionary<string, string>();
_publisherByTopic = [];
foreach (var t in _topicsByPublisher)
{
foreach (var topic in t.Value)
@@ -189,21 +171,18 @@ public class MessageDeliveryBenchmark : BaseBenchmark
// Subscribe to NumSubscribedTopics topics spread across all topics
_allSubscribedTopics = new List<string>();
var totalNumTopics = NumPublishers * NumTopicsPerPublisher;
var topicIndexStep = totalNumTopics / (NumSubscribedTopicsPerSubscriber * NumSubscribers);
if (topicIndexStep * NumSubscribedTopicsPerSubscriber * NumSubscribers != totalNumTopics)
var totalNumTopics = _numPublishers * _numTopicsPerPublisher;
var topicIndexStep = totalNumTopics / (_numSubscribedTopicsPerSubscriber * _numSubscribers);
if (topicIndexStep * _numSubscribedTopicsPerSubscriber * _numSubscribers != totalNumTopics)
{
throw new Exception(
string.Format(
"The total number of topics must be divisible by the number of subscribed topics across all subscribers. Total number of topics: {0}, topic step: {1}",
totalNumTopics,
topicIndexStep));
throw new InvalidOperationException(
$"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}");
}
var topicIndex = 0;
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];
_allSubscribedTopics.Add(topic);
@@ -9,65 +9,64 @@ using Microsoft.AspNetCore.Hosting;
using MQTTnet.AspNetCore;
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;
IMqttClient _mqttClient;
MqttApplicationMessage _message;
[GlobalSetup]
public void Setup()
{
IWebHost _host;
IMqttClient _mqttClient;
MqttApplicationMessage _message;
_host = WebHost.CreateDefaultBuilder()
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
.ConfigureServices(services => {
services
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
.AddMqttConnectionHandler();
})
.Configure(app => {
app.UseMqttServer(_ => {
[GlobalSetup]
public void Setup()
});
})
.Build();
var factory = new MqttClientFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttClient.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
_host.Dispose();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_host = WebHost.CreateDefaultBuilder()
.UseKestrel(o => o.ListenAnyIP(1883, l => l.UseMqtt()))
.ConfigureServices(services => {
services
.AddHostedMqttServer(mqttServerOptions => mqttServerOptions.WithoutDefaultEndpoint())
.AddMqttConnectionHandler();
})
.Configure(app => {
app.UseMqttServer(s => {
});
})
.Build();
var factory = new MqttClientFactory();
_mqttClient = factory.CreateMqttClient(new MqttNetEventLogger(), new MqttClientConnectionContextFactory());
_host.StartAsync().GetAwaiter().GetResult();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost").Build();
_mqttClient.ConnectAsync(clientOptions).GetAwaiter().GetResult();
_message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.Build();
}
[GlobalCleanup]
public void Cleanup()
{
_mqttClient.DisconnectAsync().GetAwaiter().GetResult();
_mqttClient.Dispose();
_host.StopAsync().GetAwaiter().GetResult();
_host.Dispose();
}
[Benchmark]
public void Send_10000_Messages()
{
for (var i = 0; i < 10000; i++)
{
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
_mqttClient.PublishAsync(_message).GetAwaiter().GetResult();
}
}
}
}
@@ -2,42 +2,40 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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;
int _bufferLength;
[GlobalSetup]
public void GlobalSetup()
{
byte[] _buffer;
int _bufferLength;
var writer = new MqttBufferWriter(1024, 1024);
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
[GlobalSetup]
public void GlobalSetup()
{
var writer = new MqttBufferWriter(1024, 1024);
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
_buffer = writer.GetBuffer();
_bufferLength = writer.Length;
}
_buffer = writer.GetBuffer();
_bufferLength = writer.Length;
}
[Benchmark]
public void Use_Span()
{
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Span()
{
var span = _buffer.AsSpan(0, _bufferLength);
Encoding.UTF8.GetString(span);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
[Benchmark]
public void Use_Encoding()
{
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
}
}
@@ -2,95 +2,93 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Formatter;
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];
byte[] _readPayload;
[GlobalCleanup]
public void GlobalCleanup()
{
readonly byte[] _demoPayload = new byte[1024];
byte[] _readPayload;
}
[GlobalCleanup]
public void GlobalCleanup()
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
[Benchmark]
public void Read_100_000_Messages()
{
var reader = new MqttBufferReader();
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
for (var i = 0; i < 100000; i++)
{
reader.Seek(0);
reader.ReadString();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadVariableByteInteger();
reader.ReadString();
reader.ReadVariableByteInteger();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadString();
reader.ReadBinaryData();
}
}
[GlobalSetup]
public void GlobalSetup()
[Benchmark]
public void Write_100_000_Messages()
{
var writer = new MqttBufferWriter(4096, 65535);
for (var i = 0; i < 100000; i++)
{
TestEnvironment.EnableLogger = false;
var writer = new MqttBufferWriter(4096, 65535);
writer.WriteString("A relative short string.");
writer.WriteBinary(_demoPayload);
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574489);
writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
_readPayload = new ArraySegment<byte>(writer.GetBuffer(), 0, writer.Length).ToArray();
}
[Benchmark]
public void Read_100_000_Messages()
{
var reader = new MqttBufferReader();
reader.SetBuffer(_readPayload, 0, _readPayload.Length);
for (var i = 0; i < 100000; i++)
{
reader.Seek(0);
reader.ReadString();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadVariableByteInteger();
reader.ReadString();
reader.ReadVariableByteInteger();
reader.ReadBinaryData();
reader.ReadByte();
reader.ReadByte();
reader.ReadString();
reader.ReadBinaryData();
}
}
[Benchmark]
public void Write_100_000_Messages()
{
var writer = new MqttBufferWriter(4096, 65535);
for (var i = 0; i < 100000; i++)
{
writer.WriteString("A relative short string.");
writer.WriteByte(0x01);
writer.WriteByte(0x02);
writer.WriteVariableByteInteger(5647382);
writer.WriteString("A relative short string.");
writer.WriteVariableByteInteger(8574589);
writer.WriteBinary(_demoPayload);
writer.WriteByte(2);
writer.WriteByte(0x02);
writer.WriteString("fjgffiogfhgfhoihgoireghreghreguhreguireoghreouighreouighreughreguiorehreuiohruiorehreuioghreug");
writer.WriteBinary(_demoPayload);
writer.Reset(0);
}
writer.Reset(0);
}
}
}
@@ -2,11 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Channel;
@@ -19,9 +16,9 @@ namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser]
public class MqttTcpChannelBenchmark : BaseBenchmark
public sealed class MqttTcpChannelBenchmark : BaseBenchmark, IDisposable
{
IMqttChannel _clientChannel;
MqttTcpChannel _clientChannel;
MqttServer _mqttServer;
IMqttChannel _serverChannel;
@@ -85,4 +82,11 @@ public class MqttTcpChannelBenchmark : BaseBenchmark
await _serverChannel.WriteAsync(buffer, true, CancellationToken.None).ConfigureAwait(false);
}
}
public void Dispose()
{
_clientChannel?.Dispose();
_mqttServer?.Dispose();
_serverChannel?.Dispose();
}
}
+102 -108
View File
@@ -2,141 +2,135 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using MQTTnet.Diagnostics;
namespace MQTTnet.Benchmarks
namespace MQTTnet.Benchmarks;
public static class Program
{
public static class Program
static int _selectedBenchmarkIndex;
static List<Type> _benchmarks;
public static void Main(string[] arguments)
{
static int _selectedBenchmarkIndex;
static List<Type> _benchmarks;
_benchmarks = CollectBenchmarks();
HandleArguments(arguments);
public static void Main(string[] arguments)
while (true)
{
_benchmarks = CollectBenchmarks();
HandleArguments(arguments);
RenderMenu();
while (true)
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
{
RenderMenu();
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.DownArrow)
_selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex++;
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = 0;
}
}
else if (key.Key == ConsoleKey.UpArrow)
{
_selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
else if (key.Key == ConsoleKey.Escape)
{
Environment.Exit(0);
}
else if (key.Key == ConsoleKey.Enter)
{
break;
_selectedBenchmarkIndex = 0;
}
}
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
else if (key.Key == ConsoleKey.UpArrow)
{
if (typeof(BaseBenchmark).IsAssignableFrom(type))
_selectedBenchmarkIndex--;
if (_selectedBenchmarkIndex < 0)
{
if (type != typeof(BaseBenchmark))
{
benchmarks.Add(type);
}
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
else if (key.Key == ConsoleKey.Escape)
{
return;
Environment.Exit(0);
}
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
else if (key.Key == ConsoleKey.Enter)
{
_selectedBenchmarkIndex = benchmarkIndex;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
break;
}
}
static void RenderMenu()
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
static List<Type> CollectBenchmarks()
{
var benchmarks = new List<Type>();
var types = typeof(Program).Assembly.GetExportedTypes();
foreach (var type in types)
{
Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
if (typeof(BaseBenchmark).IsAssignableFrom(type))
{
var benchmark = _benchmarks[index];
if (_selectedBenchmarkIndex == index)
if (type != typeof(BaseBenchmark))
{
Console.Write("-> ");
benchmarks.Add(type);
}
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
}
}
Console.WriteLine();
return benchmarks.OrderBy(b => b.Name).ToList();
}
static void HandleArguments(string[] arguments)
{
if (arguments.Length == 0)
{
return;
}
// Allow for preselection to avoid developer frustration.
if (int.TryParse(arguments[0], out var benchmarkIndex))
{
_selectedBenchmarkIndex = benchmarkIndex;
return;
}
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0], StringComparison.Ordinal));
if (_selectedBenchmarkIndex < 0)
{
_selectedBenchmarkIndex = 0;
}
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
{
_selectedBenchmarkIndex = _benchmarks.Count - 1;
}
}
static void RenderMenu()
{
Console.Clear();
Console.WriteLine($"MQTTnet - Benchmarks");
Console.WriteLine("-----------------------------------------------");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Press arrow keys for benchmark selection");
Console.WriteLine("Press Enter for benchmark execution");
Console.WriteLine("Press Esc for exit");
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine();
for (var index = 0; index < _benchmarks.Count; index++)
{
var benchmark = _benchmarks[index];
if (_selectedBenchmarkIndex == index)
{
Console.Write("-> ");
}
else
{
Console.Write(" ");
}
Console.WriteLine(benchmark.Name);
}
Console.WriteLine();
}
}
@@ -1,240 +1,97 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using MQTTnet.Adapter;
using MQTTnet.AspNetCore;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using System;
using System.Buffers;
using System.IO;
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)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class ReaderExtensionsBenchmark
{
MqttPacketFormatterAdapter mqttPacketFormatter;
MemoryStream stream;
MqttPacketFormatterAdapter _mqttPacketFormatter;
MemoryStream _stream;
[GlobalSetup]
public void GlobalSetup()
{
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
[GlobalSetup]
public void GlobalSetup()
{
_mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
var mqttMessage = new MqttApplicationMessageBuilder()
.WithTopic("topic")
.WithPayload(new byte[10 * 1024])
.Build();
var packet = MqttPublishPacketFactory.Create(mqttMessage);
var buffer = mqttPacketFormatter.Encode(packet);
stream = new MemoryStream();
stream.Write(buffer.Packet);
stream.Write(buffer.Payload.ToArray());
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]
public async Task After()
{
stream.Position = 0;
var input = PipeReader.Create(stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
public static class ReaderExtensions_Before
{
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
in ReadOnlySequence<byte> input,
out MqttPacket packet,
out SequencePosition consumed,
out SequencePosition observed,
out int bytesRead)
{
ArgumentNullException.ThrowIfNull(formatter);
packet = null;
consumed = input.Start;
observed = input.End;
bytesRead = 0;
var copy = input;
if (copy.Length < 2)
{
return false;
}
var fixedHeader = copy.First.Span[0];
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
{
return false;
}
if (copy.Length < bodyLength)
{
return false;
}
var bodySlice = copy.Slice(0, bodyLength);
var buffer = GetMemory(bodySlice).ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{
formatter.DetectProtocolVersion(receivedMqttPacket);
}
packet = formatter.Decode(receivedMqttPacket);
consumed = bodySlice.End;
observed = bodySlice.End;
bytesRead = headerLength + bodyLength;
return true;
}
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
{
if (input.IsSingleSegment)
{
return input.First;
}
// Should be rare
return input.ToArray();
}
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
{
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
var multiplier = 1;
var value = 0;
byte encodedByte;
var index = 1;
headerLength = 0;
bodyLength = 0;
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
do
{
if (index == temp.Length)
{
return false;
}
encodedByte = temp[index];
index++;
value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128)
{
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
}
multiplier *= 128;
} while ((encodedByte & 128) != 0);
input = input.Slice(index);
headerLength = index;
bodyLength = value;
return true;
}
}
var packet = MqttPublishPacketFactory.Create(mqttMessage);
var buffer = _mqttPacketFormatter.Encode(packet);
_stream = new MemoryStream();
_stream.Write(buffer.Packet);
_stream.Write(buffer.Payload.ToArray());
_mqttPacketFormatter.Cleanup();
}
}
[Benchmark]
public async Task After()
{
_stream.Position = 0;
var input = PipeReader.Create(_stream);
while (true)
{
ReadResult readResult;
var readTask = input.ReadAsync();
if (readTask.IsCompleted)
{
readResult = readTask.Result;
}
else
{
readResult = await readTask.ConfigureAwait(false);
}
var buffer = readResult.Buffer;
var consumed = buffer.Start;
var observed = buffer.Start;
try
{
if (!buffer.IsEmpty)
{
if (_mqttPacketFormatter.TryDecode(buffer, out _, out consumed, out observed, out _))
{
break;
}
}
else if (readResult.IsCompleted)
{
throw new MqttCommunicationException("Connection Aborted");
}
}
finally
{
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
// before yielding the read again.
input.AdvanceTo(consumed, observed);
}
}
}
public void Dispose()
{
_stream?.Dispose();
}
public async ValueTask DisposeAsync()
{
if (_stream != null)
{
await _stream.DisposeAsync();
}
}
}
@@ -1,30 +1,28 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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]
public void GlobalSetup()
{
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Receiving_Client()
{
new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
}
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Receiving_Client()
{
//new Load_Tests().Handle_100_000_Messages_In_Receiving_Client().GetAwaiter().GetResult();
}
}
@@ -1,69 +1,78 @@
using System.Buffers;
using System.IO.Pipelines;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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)]
[RPlotExporter, RankColumn]
[MemoryDiagnoser]
public class SendPacketAsyncBenchmark : BaseBenchmark
MqttPacketBuffer _buffer;
MemoryStream _stream;
[Benchmark]
public async ValueTask After()
{
MemoryStream stream;
MqttPacketBuffer buffer;
_stream.Position = 0;
var output = PipeWriter.Create(_stream);
[GlobalSetup]
public void GlobalSetup()
if (_buffer.Payload.Length == 0)
{
stream = new MemoryStream(1024);
var packet = new ArraySegment<byte>(new byte[10]);
buffer = new MqttPacketBuffer(packet);
await output.WriteAsync(_buffer.Packet).ConfigureAwait(false);
}
[Benchmark(Baseline = true)]
public async ValueTask Before()
else
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
WritePacketBuffer(output, buffer);
await output.FlushAsync();
}
[Benchmark]
public async ValueTask After()
{
stream.Position = 0;
var output = PipeWriter.Create(stream);
if (buffer.Payload.Length == 0)
{
await output.WriteAsync(buffer.Packet).ConfigureAwait(false);
}
else
{
WritePacketBuffer(output, buffer);
await output.FlushAsync().ConfigureAwait(false);
}
}
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span.Slice(buffer.Packet.Count));
output.Advance(buffer.Length);
WritePacketBuffer(output, _buffer);
await output.FlushAsync().ConfigureAwait(false);
}
}
[Benchmark(Baseline = true)]
public async ValueTask Before()
{
_stream.Position = 0;
var output = PipeWriter.Create(_stream);
WritePacketBuffer(output, _buffer);
await output.FlushAsync();
}
public void Dispose()
{
_stream?.Dispose();
}
public async ValueTask DisposeAsync()
{
if (_stream != null)
{
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)
{
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
var span = output.GetSpan(buffer.Length);
buffer.Packet.AsSpan().CopyTo(span);
buffer.Payload.CopyTo(span[buffer.Packet.Count..]);
output.Advance(buffer.Length);
}
}
@@ -4,10 +4,7 @@
using BenchmarkDotNet.Attributes;
using MQTTnet.Packets;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Channel;
using MQTTnet.Formatter;
@@ -17,103 +14,104 @@ using MQTTnet.Diagnostics.Logger;
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;
ArraySegment<byte> _serializedPacket;
MqttV3PacketFormatter _serializer;
MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
{
MqttPacket _packet;
ArraySegment<byte> _serializedPacket;
IMqttPacketFormatter _serializer;
MqttBufferWriter _bufferWriter;
[GlobalSetup]
public void GlobalSetup()
_packet = new MqttPublishPacket
{
_packet = new MqttPublishPacket
{
Topic = "A"
};
Topic = "A"
};
_bufferWriter = new MqttBufferWriter(4096, 65535);
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
_serializedPacket = _serializer.Encode(_packet).Join();
}
_bufferWriter = new MqttBufferWriter(4096, 65535);
_serializer = new MqttV3PacketFormatter(_bufferWriter, MqttProtocolVersion.V311);
_serializedPacket = _serializer.Encode(_packet).Join();
}
[Benchmark]
public void Serialize_10000_Messages()
[Benchmark]
public void Serialize10000Messages()
{
for (var i = 0; i < 10000; i++)
{
for (var i = 0; i < 10000; i++)
{
_serializer.Encode(_packet);
_bufferWriter.Cleanup();
}
}
[Benchmark]
public void Deserialize_10000_Messages()
{
var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
channel.Reset();
var header = reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
class BenchmarkMqttChannel : IMqttChannel
{
readonly ArraySegment<byte> _buffer;
int _position;
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
{
_buffer = buffer;
_position = _buffer.Offset;
}
public EndPoint RemoteEndPoint { get; set; }
public bool IsSecureConnection { get; } = false;
public X509Certificate2 ClientCertificate { get; }
public void Reset()
{
_position = _buffer.Offset;
}
public Task ConnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Array.Copy(_buffer.Array, _position, buffer, offset, count);
_position += count;
return Task.FromResult(count);
}
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public void Dispose()
{
}
_serializer.Encode(_packet);
_bufferWriter.Cleanup();
}
}
}
[Benchmark]
public void Deserialize10000Messages()
{
var channel = new BenchmarkMqttChannel(_serializedPacket);
var reader = new MqttChannelAdapter(channel, new MqttPacketFormatterAdapter(new MqttBufferWriter(4096, 65535)), new MqttNetEventLogger());
for (var i = 0; i < 10000; i++)
{
channel.Reset();
reader.ReceivePacketAsync(CancellationToken.None).GetAwaiter().GetResult();
}
}
sealed class BenchmarkMqttChannel : IMqttChannel
{
readonly ArraySegment<byte> _buffer;
int _position;
public BenchmarkMqttChannel(ArraySegment<byte> buffer)
{
_buffer = buffer;
_position = _buffer.Offset;
}
public EndPoint RemoteEndPoint { get; set; }
public EndPoint LocalEndPoint { get; set; }
public bool IsSecureConnection { get; }
public X509Certificate2 ClientCertificate { get; set; }
public void Reset()
{
_position = _buffer.Offset;
}
public Task ConnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task DisconnectAsync(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Array.Copy(_buffer!.Array!, _position, buffer, offset, count);
_position += count;
return Task.FromResult(count);
}
public Task WriteAsync(ReadOnlySequence<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public void Dispose()
{
}
}
}
@@ -5,36 +5,34 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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]
public void GlobalSetup()
{
[GlobalSetup]
public void GlobalSetup()
{
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Server_MqttClient()
{
new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
}
//[Benchmark]
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
{
new Load_Tests().Handle_100_000_Messages_In_Low_Level_Client().GetAwaiter().GetResult();
}
TestEnvironment.EnableLogger = false;
}
[GlobalCleanup]
public void GlobalCleanup()
{
}
[Benchmark]
public void Handle_100_000_Messages_In_Server_MqttClient()
{
//new Load_Tests().Handle_100_000_Messages_In_Server().GetAwaiter().GetResult();
}
[Benchmark]
public void Handle_100_000_Messages_In_Server_LowLevelMqttClient()
{
//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.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using MQTTnet.Server;
@@ -30,7 +28,7 @@ public class SubscribeBenchmark : BaseBenchmark
[GlobalSetup]
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();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
+60 -63
View File
@@ -5,76 +5,73 @@
using System.IO.Pipelines;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
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 _server;
[GlobalSetup]
public void Setup()
{
IDuplexPipe _client;
IDuplexPipe _server;
var server = new TcpListener(IPAddress.Any, 1883);
[GlobalSetup]
public void Setup()
server.Start(1);
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
}
[Benchmark]
public async Task Send_10000_Chunks_Pipe()
{
var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
var input = _client.Input;
while (read < expected)
{
var server = new TcpListener(IPAddress.Any, 1883);
server.Start(1);
var task = Task.Run(() => server.AcceptSocket());
var clientConnection = new SocketConnection(new IPEndPoint(IPAddress.Loopback, 1883));
clientConnection.StartAsync().GetAwaiter().GetResult();
_client = clientConnection.Transport;
var serverConnection = new SocketConnection(task.GetAwaiter().GetResult());
serverConnection.StartAsync().GetAwaiter().GetResult();
_server = serverConnection.Transport;
}
[Benchmark]
public async Task Send_10000_Chunks_Pipe()
{
var size = 5;
var iterations = 10000;
await Task.WhenAll(WriteAsync(iterations, size), ReadAsync(iterations, size));
}
async Task ReadAsync(int iterations, int size)
{
await Task.Yield();
var expected = iterations * size;
long read = 0;
var input = _client.Input;
while (read < expected)
{
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
read += readresult.Buffer.Length;
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
var output = _server.Output;
for (var i = 0; i < iterations; i++)
{
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
}
var readresult = await input.ReadAsync(CancellationToken.None).ConfigureAwait(false);
input.AdvanceTo(readresult.Buffer.End);
read += readresult.Buffer.Length;
}
}
}
async Task WriteAsync(int iterations, int size)
{
await Task.Yield();
var output = _server.Output;
for (var i = 0; i < iterations; i++)
{
await output.WriteAsync(new byte[size], CancellationToken.None).ConfigureAwait(false);
}
}
}
@@ -2,269 +2,32 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace MQTTnet.Benchmarks
namespace MQTTnet.Benchmarks;
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark
{
[SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter]
[MemoryDiagnoser]
public class TopicFilterComparerBenchmark : BaseBenchmark
readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
{
static readonly char[] TopicLevelSeparator = { '/' };
readonly string _longTopic =
"AAAAAAAAAAAAAssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssshhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC";
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod()
for (var i = 0; i < 100000; i++)
{
for (var i = 0; i < 100000; i++)
{
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparer.Compare("/finance", "+/+");
MqttTopicFilterComparer.Compare("/finance", "/+");
MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_LoopMethod_Without_Pointer()
{
for (var i = 0; i < 100000; i++)
{
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparerWithoutPointer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "/+");
MqttTopicFilterComparerWithoutPointer.Compare("/finance", "+");
MqttTopicFilterComparerWithoutPointer.Compare(_longTopic, _longTopic);
}
}
[Benchmark]
public void MqttTopicFilterComparer_10000_StringSplitMethod()
{
for (var i = 0; i < 100000; i++)
{
LegacyMethodByStringSplit("sport/tennis/player1", "sport/#");
LegacyMethodByStringSplit("sport/tennis/player1/ranking", "sport/#/ranking");
LegacyMethodByStringSplit("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
LegacyMethodByStringSplit("sport/tennis/player1", "sport/tennis/+");
LegacyMethodByStringSplit("/finance", "+/+");
LegacyMethodByStringSplit("/finance", "/+");
LegacyMethodByStringSplit("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
[GlobalSetup]
public void Setup()
{
}
static bool LegacyMethodByStringSplit(string topic, string filter)
{
ArgumentNullException.ThrowIfNull(topic);
ArgumentNullException.ThrowIfNull(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;
}
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1/ranking", "sport/#/ranking");
MqttTopicFilterComparer.Compare("sport/tennis/player1/score/wimbledon", "sport/+/player1/#");
MqttTopicFilterComparer.Compare("sport/tennis/player1", "sport/tennis/+");
MqttTopicFilterComparer.Compare("/finance", "+/+");
MqttTopicFilterComparer.Compare("/finance", "/+");
MqttTopicFilterComparer.Compare("/finance", "+");
MqttTopicFilterComparer.Compare(_longTopic, _longTopic);
}
}
}
+61 -65
View File
@@ -1,84 +1,80 @@
using System;
using System.Collections.Generic;
namespace MQTTnet.Benchmarks;
namespace MQTTnet.Benchmarks
public class TopicGenerator
{
public class TopicGenerator
public static void Generate(
int numPublishers,
int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
{
public static void Generate(
int numPublishers,
int numTopicsPerPublisher,
out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
// Find some reasonable distribution across three topic levels
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
{
topicsByPublisher = new Dictionary<string, List<string>>();
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
topicsPerLevel = 1;
}
// Find some reasonable distribution across three topic levels
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
if (topicsPerLevel <= 0)
var numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
{
maxNumLevel3Topics = 1;
}
for (var p = 0; p < numPublishers; ++p)
{
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{
topicsPerLevel = 1;
}
var numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0)
{
maxNumLevel3Topics = 1;
}
for (var p = 0; p < numPublishers; ++p)
{
var publisherTopicCount = 0;
var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
{
for (var l2 = 0; l2 < numLevel2Topics; ++l2)
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
{
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
if (publisherTopicCount >= numTopicsPerPublisher)
{
if (publisherTopicCount >= numTopicsPerPublisher)
{
break;
}
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
++publisherTopicCount;
break;
}
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0)
{
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
}
if (l1 == 0 && l3 == 0)
{
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
}
++publisherTopicCount;
}
}
}
}
}
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
static void AddPublisherTopic(string publisherName, string topic, Dictionary<string, List<string>> topicsByPublisher)
{
List<string> topicList;
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
{
List<string> topicList;
if (!topicsByPublisher.TryGetValue(publisherName, out topicList))
{
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
topicList = new List<string>();
topicsByPublisher.Add(publisherName, topicList);
}
topicList.Add(topic);
}
}
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using MQTTnet.Server;
@@ -14,15 +12,15 @@ public class UnsubscribeBenchmark : BaseBenchmark
{
const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
IMqttClient _mqttClient;
MqttServer _mqttServer;
List<string> _topics;
[GlobalSetup]
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();
var serverOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
@@ -2,29 +2,26 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.Rpc;
namespace MQTTnet.Extensions.Rpc
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
{
public sealed class DefaultMqttRpcClientTopicGenerationStrategy : IMqttRpcClientTopicGenerationStrategy
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
{
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
ArgumentNullException.ThrowIfNull(context);
if (context.MethodName.Contains('/') || context.MethodName.Contains('+') || context.MethodName.Contains('#'))
{
ArgumentNullException.ThrowIfNull(context);
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
{
throw new ArgumentException("The method name cannot contain /, + or #.");
}
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
throw new ArgumentException("The method name cannot contain /, + or #.");
}
var requestTopic = $"MQTTnet.RPC/{Guid.NewGuid():N}/{context.MethodName}";
var responseTopic = requestTopic + "/response";
return new MqttRpcTopicPair
{
RequestTopic = requestTopic,
ResponseTopic = responseTopic
};
}
}
}
@@ -2,18 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.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(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
}
Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string,object> parameters = null);
Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default);
}
@@ -2,10 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// 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">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.Rpc</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Rpc</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<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>
<Authors>The contributors of MQTTnet</Authors>
<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>
<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>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<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>
<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>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup>
@@ -2,28 +2,25 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.Rpc;
namespace MQTTnet.Extensions.Rpc
public static class MqttFactoryExtensions
{
public static class MqttFactoryExtensions
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
{
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory clientFactory, IMqttClient mqttClient)
{
return clientFactory.CreateMqttRpcClient(
mqttClient,
new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
return clientFactory.CreateMqttRpcClient(
mqttClient,
new MqttRpcClientOptions
{
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
}
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{
ArgumentNullException.ThrowIfNull(mqttClient);
ArgumentNullException.ThrowIfNull(rpcClientOptions);
public static IMqttRpcClient CreateMqttRpcClient(this MqttClientFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
{
ArgumentNullException.ThrowIfNull(mqttClient);
ArgumentNullException.ThrowIfNull(rpcClientOptions);
return new MqttRpcClient(mqttClient, rpcClientOptions);
}
return new MqttRpcClient(mqttClient, rpcClientOptions);
}
}
+106 -114
View File
@@ -2,140 +2,132 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
namespace MQTTnet.Extensions.Rpc;
public sealed class MqttRpcClient : IMqttRpcClient
{
public sealed class MqttRpcClient : IMqttRpcClient
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
{
readonly IMqttClient _mqttClient;
readonly MqttRpcClientOptions _options;
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
}
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
public void Dispose()
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
tcs.Value.TrySetCanceled();
}
public void Dispose()
_waitingCalls.Clear();
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
{
using var timeoutToken = new CancellationTokenSource(timeout);
try
{
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
foreach (var tcs in _waitingCalls)
{
tcs.Value.TrySetCanceled();
}
_waitingCalls.Clear();
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
}
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null)
catch (OperationCanceledException exception)
{
using (var timeoutToken = new CancellationTokenSource(timeout))
if (timeoutToken.IsCancellationRequested)
{
try
{
return await ExecuteAsync(methodName, payload, qualityOfServiceLevel, parameters, timeoutToken.Token).ConfigureAwait(false);
}
catch (OperationCanceledException exception)
{
if (timeoutToken.IsCancellationRequested)
{
throw new MqttCommunicationTimedOutException(exception);
}
throw;
}
}
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(methodName);
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
throw new MqttCommunicationTimedOutException(exception);
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
throw;
}
}
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, IDictionary<string, object> parameters = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(methodName);
var context = new TopicGenerationContext(_mqttClient, _options, methodName, parameters, qualityOfServiceLevel);
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic;
if (string.IsNullOrWhiteSpace(requestTopic))
{
throw new MqttProtocolViolationException("RPC request topic is empty.");
}
if (string.IsNullOrWhiteSpace(responseTopic))
{
throw new MqttProtocolViolationException("RPC response topic is empty.");
}
var requestMessageBuilder = new MqttApplicationMessageBuilder().WithTopic(requestTopic).WithPayload(payload).WithQualityOfServiceLevel(qualityOfServiceLevel);
if (_mqttClient.Options.ProtocolVersion == MqttProtocolVersion.V500)
{
requestMessageBuilder.WithResponseTopic(responseTopic);
}
var requestMessage = requestMessageBuilder.Build();
try
{
var awaitable = new AsyncTaskCompletionSource<byte[]>();
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
{
throw new InvalidOperationException();
}
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
using (cancellationToken.Register(
() =>
{
awaitable.TrySetCanceled();
}))
{
return await awaitable.Task.ConfigureAwait(false);
}
}
finally
{
_waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
}
}
Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{
return CompletedTask.Instance;
}
var payloadBuffer = eventArgs.ApplicationMessage.Payload.ToArray();
awaitable.TrySetResult(payloadBuffer);
// Set this message to handled to that other code can avoid execution etc.
eventArgs.IsHandled = true;
return CompletedTask.Instance;
}
}
@@ -2,23 +2,19 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
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.
// 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,27 +2,24 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Extensions.Rpc;
namespace MQTTnet.Extensions.Rpc
public sealed class MqttRpcClientOptionsBuilder
{
public sealed class MqttRpcClientOptionsBuilder
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptions Build()
{
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
public MqttRpcClientOptions Build()
return new MqttRpcClientOptions
{
return new MqttRpcClientOptions
{
TopicGenerationStrategy = _topicGenerationStrategy
};
}
TopicGenerationStrategy = _topicGenerationStrategy
};
}
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
return this;
}
}
@@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Rpc
{
public sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
namespace MQTTnet.Extensions.Rpc;
public string ResponseTopic { get; set; }
}
}
public sealed class MqttRpcTopicPair
{
public string RequestTopic { get; set; }
public string ResponseTopic { get; set; }
}
@@ -2,41 +2,38 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.Rpc
namespace MQTTnet.Extensions.Rpc;
public sealed class TopicGenerationContext
{
public sealed class TopicGenerationContext
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName,
IDictionary<string, object> parameters, MqttQualityOfServiceLevel qualityOfServiceLevel)
{
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
Parameters = parameters;
QualityOfServiceLevel = qualityOfServiceLevel;
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
Options = options ?? throw new ArgumentNullException(nameof(options));
}
public string MethodName { get; }
public IDictionary<string, object> Parameters { get; }
public IMqttClient MqttClient { get; }
public MqttRpcClientOptions Options { get; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
/// that defines the guarantee of delivery for a specific message.
/// There are 3 QoS levels in MQTT:
/// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At least once (1): Message gets delivered at least once (one time or more often).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>MQTTnet.Extensions.Socks5</AssemblyName>
<RootNamespace>MQTTnet.Extensions.Socks5</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Description>This is an extension library which adds SOCKS5 proxy support (RFC 1928 / RFC 1929) to the MQTTnet TCP transport.</Description>
<PackageId>MQTTnet.Extensions.Socks5</PackageId>
<PackageTags>MQTT Message Queue Telemetry Transport MQTTClient SOCKS SOCKS5 Proxy Tunnel Networking IoT InternetOfThings</PackageTags>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MQTTnet\MQTTnet.csproj"/>
</ItemGroup>
</Project>
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// Sugar extensions for configuring a SOCKS5 proxy on the MQTT client options builder.
/// </summary>
public static class MqttClientOptionsBuilderExtensions
{
/// <summary>
/// Configures the client to tunnel its TCP connection to the broker through a SOCKS5
/// proxy. A TCP transport (via <c>WithTcpServer(...)</c> or <c>WithEndPoint(...)</c>)
/// must be configured first.
/// </summary>
public static MqttClientOptionsBuilder WithSocks5Proxy(this MqttClientOptionsBuilder builder, Socks5ProxyOptions options)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(options);
return builder.WithStreamProvider(new Socks5StreamProvider(options));
}
/// <summary>
/// Configures the client to tunnel its TCP connection to the broker through a SOCKS5
/// proxy using the fluent <see cref="Socks5ProxyOptionsBuilder"/>.
/// </summary>
public static MqttClientOptionsBuilder WithSocks5Proxy(this MqttClientOptionsBuilder builder, Action<Socks5ProxyOptionsBuilder> configure)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);
var optionsBuilder = new Socks5ProxyOptionsBuilder();
configure(optionsBuilder);
return WithSocks5Proxy(builder, optionsBuilder.Build());
}
}
@@ -0,0 +1,109 @@
// 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.Net;
using System.Net.Sockets;
using MQTTnet.Channel;
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// Wraps a <see cref="NetworkStream"/> opened by the SOCKS5 stream provider so
/// <c>MqttTcpChannel</c> can read the original socket's endpoints via
/// <see cref="IMqttClientStreamEndPoints"/> while the rest of the I/O delegates to the
/// underlying stream.
/// </summary>
sealed class Socks5NetworkStream : Stream, IMqttClientStreamEndPoints
{
readonly NetworkStream _inner;
public Socks5NetworkStream(NetworkStream inner, EndPoint localEndPoint, EndPoint remoteEndPoint)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
LocalEndPoint = localEndPoint;
RemoteEndPoint = remoteEndPoint;
}
public EndPoint LocalEndPoint { get; }
public EndPoint RemoteEndPoint { get; }
public override bool CanRead => _inner.CanRead;
public override bool CanSeek => _inner.CanSeek;
public override bool CanWrite => _inner.CanWrite;
public override bool CanTimeout => _inner.CanTimeout;
public override long Length => _inner.Length;
public override long Position
{
get => _inner.Position;
set => _inner.Position = value;
}
public override int ReadTimeout
{
get => _inner.ReadTimeout;
set => _inner.ReadTimeout = value;
}
public override int WriteTimeout
{
get => _inner.WriteTimeout;
set => _inner.WriteTimeout = value;
}
public override void Flush() => _inner.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => _inner.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count) => _inner.Read(buffer, offset, count);
public override int Read(Span<byte> buffer) => _inner.Read(buffer);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> _inner.ReadAsync(buffer, offset, count, cancellationToken);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
=> _inner.ReadAsync(buffer, cancellationToken);
public override long Seek(long offset, SeekOrigin origin) => _inner.Seek(offset, origin);
public override void SetLength(long value) => _inner.SetLength(value);
public override void Write(byte[] buffer, int offset, int count) => _inner.Write(buffer, offset, count);
public override void Write(ReadOnlySpan<byte> buffer) => _inner.Write(buffer);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> _inner.WriteAsync(buffer, offset, count, cancellationToken);
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
=> _inner.WriteAsync(buffer, cancellationToken);
public override void Close()
{
_inner.Close();
base.Close();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_inner.Dispose();
}
base.Dispose(disposing);
}
public override async ValueTask DisposeAsync()
{
await _inner.DisposeAsync().ConfigureAwait(false);
await base.DisposeAsync().ConfigureAwait(false);
}
}
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// Protocol constants for SOCKS5 (RFC 1928) and the username/password sub-negotiation
/// (RFC 1929). Internal use only.
/// </summary>
internal static class Socks5Protocol
{
public const byte Version = 0x05;
public const byte AuthSubNegotiationVersion = 0x01;
public const byte MethodNoAuth = 0x00;
public const byte MethodGssApi = 0x01;
public const byte MethodUsernamePassword = 0x02;
public const byte MethodNoneAcceptable = 0xFF;
public const byte CommandConnect = 0x01;
public const byte Reserved = 0x00;
public const byte AddressTypeIPv4 = 0x01;
public const byte AddressTypeDomainName = 0x03;
public const byte AddressTypeIPv6 = 0x04;
public const byte ReplySucceeded = 0x00;
public const byte ReplyGeneralFailure = 0x01;
public const byte ReplyConnectionNotAllowed = 0x02;
public const byte ReplyNetworkUnreachable = 0x03;
public const byte ReplyHostUnreachable = 0x04;
public const byte ReplyConnectionRefused = 0x05;
public const byte ReplyTtlExpired = 0x06;
public const byte ReplyCommandNotSupported = 0x07;
public const byte ReplyAddressTypeNotSupported = 0x08;
public const int MaxStringLength = 255;
}
@@ -0,0 +1,65 @@
// 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.Net.Sockets;
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// Options for the SOCKS5 stream provider (RFC 1928 / RFC 1929).
/// A new provider can be constructed from these options via
/// <see cref="MqttClientOptionsBuilderExtensions.WithSocks5Proxy(MqttClientOptionsBuilder, Socks5ProxyOptions)"/>.
/// </summary>
public sealed class Socks5ProxyOptions
{
/// <summary>
/// The SOCKS5 proxy host name or IP literal. Must be set.
/// </summary>
public string Host { get; set; }
/// <summary>
/// The SOCKS5 proxy port. Defaults to 1080.
/// </summary>
public int Port { get; set; } = 1080;
/// <summary>
/// The user name used for the username/password authentication sub-negotiation (RFC 1929).
/// When <c>null</c> or empty, the client will only offer the "no authentication" method.
/// </summary>
public string Username { get; set; }
/// <summary>
/// The password (raw bytes per RFC 1929) used for the username/password authentication
/// sub-negotiation. Must be 1..255 bytes when <see cref="Username"/> is set.
/// </summary>
public byte[] Password { get; set; }
/// <summary>
/// When <c>true</c> (the default) the broker hostname is forwarded to the proxy as a
/// SOCKS5 <c>DOMAINNAME</c> address type, so DNS resolution happens at the proxy
/// (typical when the broker is behind the proxy in a private network).
/// When <c>false</c>, the hostname is resolved locally before sending the CONNECT request
/// and an IPv4 / IPv6 address type is used.
/// </summary>
public bool ResolveDnsRemotely { get; set; } = true;
/// <summary>
/// The maximum time allowed for the full SOCKS5 handshake (TCP connect to proxy + greeting
/// + optional auth + CONNECT). The handshake will be cancelled and a
/// <see cref="MQTTnet.Exceptions.MqttProxyException"/> will be thrown when this elapses.
/// Defaults to 30 seconds.
/// </summary>
public TimeSpan HandshakeTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>
/// Address family preference for the socket connecting to the proxy and for local DNS
/// resolution when <see cref="ResolveDnsRemotely"/> is <c>false</c>.
/// </summary>
public AddressFamily AddressFamily { get; set; } = AddressFamily.Unspecified;
public override string ToString()
{
return $"{Host}:{Port}";
}
}
@@ -0,0 +1,94 @@
// 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.Net.Sockets;
using System.Text;
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// Fluent builder for <see cref="Socks5ProxyOptions"/>.
/// </summary>
public sealed class Socks5ProxyOptionsBuilder
{
readonly Socks5ProxyOptions _options = new();
public Socks5ProxyOptions Build()
{
if (string.IsNullOrEmpty(_options.Host))
{
throw new InvalidOperationException("A SOCKS5 proxy host must be configured via WithHost.");
}
if (_options.Port <= 0 || _options.Port > 65535)
{
throw new InvalidOperationException("The SOCKS5 proxy port must be in the range 1..65535.");
}
if (_options.HandshakeTimeout <= TimeSpan.Zero)
{
throw new InvalidOperationException("The SOCKS5 handshake timeout must be greater than zero.");
}
if (string.IsNullOrEmpty(_options.Username) && _options.Password is { Length: > 0 })
{
throw new InvalidOperationException("A SOCKS5 password cannot be configured without a username.");
}
if (!string.IsNullOrEmpty(_options.Username) && (_options.Password == null || _options.Password.Length == 0))
{
throw new InvalidOperationException("A SOCKS5 password must be configured when a username is set.");
}
return _options;
}
public Socks5ProxyOptionsBuilder WithHost(string host)
{
_options.Host = host ?? throw new ArgumentNullException(nameof(host));
return this;
}
public Socks5ProxyOptionsBuilder WithPort(int port)
{
_options.Port = port;
return this;
}
public Socks5ProxyOptionsBuilder WithCredentials(string username, string password)
{
_options.Username = username;
_options.Password = password != null ? Encoding.UTF8.GetBytes(password) : null;
return this;
}
public Socks5ProxyOptionsBuilder WithCredentials(string username, byte[] password)
{
_options.Username = username;
_options.Password = password;
return this;
}
/// <summary>
/// Controls whether the broker hostname is forwarded to the proxy (remote DNS, default) or
/// resolved locally before issuing the CONNECT request.
/// </summary>
public Socks5ProxyOptionsBuilder WithResolveDnsRemotely(bool resolveDnsRemotely = true)
{
_options.ResolveDnsRemotely = resolveDnsRemotely;
return this;
}
public Socks5ProxyOptionsBuilder WithHandshakeTimeout(TimeSpan timeout)
{
_options.HandshakeTimeout = timeout;
return this;
}
public Socks5ProxyOptionsBuilder WithAddressFamily(AddressFamily addressFamily)
{
_options.AddressFamily = addressFamily;
return this;
}
}
@@ -0,0 +1,507 @@
// 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.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;
using MQTTnet.Channel;
using MQTTnet.Exceptions;
namespace MQTTnet.Extensions.Socks5;
/// <summary>
/// <see cref="IMqttClientStreamProvider"/> implementation that tunnels the connection through
/// a SOCKS5 proxy (RFC 1928) with optional username/password authentication (RFC 1929).
/// </summary>
public sealed class Socks5StreamProvider : IMqttClientStreamProvider
{
readonly Socks5ProxyOptions _options;
public Socks5StreamProvider(Socks5ProxyOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
if (string.IsNullOrEmpty(_options.Host))
{
throw new ArgumentException("Socks5ProxyOptions.Host must be set.", nameof(options));
}
if (_options.Port <= 0 || _options.Port > 65535)
{
throw new ArgumentOutOfRangeException(nameof(options), _options.Port, "Socks5ProxyOptions.Port must be in the range 1..65535.");
}
if (_options.HandshakeTimeout <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(options), _options.HandshakeTimeout, "Socks5ProxyOptions.HandshakeTimeout must be greater than zero.");
}
if (string.IsNullOrEmpty(_options.Username) && _options.Password is { Length: > 0 })
{
throw new ArgumentException("Socks5ProxyOptions.Password cannot be set without Socks5ProxyOptions.Username.", nameof(options));
}
if (!string.IsNullOrEmpty(_options.Username) && (_options.Password == null || _options.Password.Length == 0))
{
throw new ArgumentException("Socks5ProxyOptions.Password must be set when Socks5ProxyOptions.Username is set.", nameof(options));
}
}
public async Task<Stream> ConnectAsync(EndPoint brokerEndPoint, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(brokerEndPoint);
using var timeoutCts = new CancellationTokenSource(_options.HandshakeTimeout);
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token);
var handshakeToken = linkedCts.Token;
var addressFamily = _options.AddressFamily;
Socket socket = addressFamily == AddressFamily.Unspecified
? new Socket(SocketType.Stream, ProtocolType.Tcp)
: new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
NetworkStream networkStream = null;
try
{
try
{
await socket.ConnectAsync(_options.Host, _options.Port, handshakeToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyUnreachable,
$"Timed out connecting to SOCKS5 proxy after {_options.HandshakeTimeout}.",
_options.ToString());
}
catch (Exception ex) when (ex is SocketException or IOException)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyUnreachable,
$"Could not connect to SOCKS5 proxy: {ex.Message}",
_options.ToString(),
ex);
}
networkStream = new NetworkStream(socket, ownsSocket: true);
try
{
await PerformGreetingAndAuthAsync(networkStream, handshakeToken).ConfigureAwait(false);
await PerformConnectAsync(networkStream, brokerEndPoint, handshakeToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Timed out during SOCKS5 handshake after {_options.HandshakeTimeout}.",
_options.ToString());
}
catch (EndOfStreamException ex)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
"The SOCKS5 proxy closed the connection during the handshake.",
_options.ToString(),
ex);
}
catch (Exception ex) when (ex is IOException or SocketException)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"I/O error during SOCKS5 handshake: {ex.Message}",
_options.ToString(),
ex);
}
// Hand off the stream — ownership transfers to the caller.
var result = new Socks5NetworkStream(networkStream, socket.LocalEndPoint, socket.RemoteEndPoint);
networkStream = null;
socket = null;
return result;
}
finally
{
networkStream?.Dispose();
socket?.Dispose();
}
}
async Task PerformGreetingAndAuthAsync(NetworkStream stream, CancellationToken cancellationToken)
{
var hasCredentials = !string.IsNullOrEmpty(_options.Username);
// VER NMETHODS METHOD[1..n]
// We always offer no-auth; we additionally offer user/pass when credentials are configured.
byte[] greeting;
if (hasCredentials)
{
greeting = [Socks5Protocol.Version, 0x02, Socks5Protocol.MethodNoAuth, Socks5Protocol.MethodUsernamePassword];
}
else
{
greeting = [Socks5Protocol.Version, 0x01, Socks5Protocol.MethodNoAuth];
}
await stream.WriteAsync(greeting, cancellationToken).ConfigureAwait(false);
// VER METHOD
var greetingResponse = new byte[2];
await ReadExactlyAsync(stream, greetingResponse, cancellationToken).ConfigureAwait(false);
if (greetingResponse[0] != Socks5Protocol.Version)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Unexpected SOCKS version in greeting response: 0x{greetingResponse[0]:X2}.",
_options.ToString());
}
var chosenMethod = greetingResponse[1];
switch (chosenMethod)
{
case Socks5Protocol.MethodNoAuth:
if (hasCredentials)
{
// Server accepted no-auth even though we offered credentials: fine, continue.
}
return;
case Socks5Protocol.MethodUsernamePassword:
if (!hasCredentials)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
"The SOCKS5 server selected username/password authentication but no credentials are configured.",
_options.ToString());
}
await PerformUsernamePasswordAuthAsync(stream, cancellationToken).ConfigureAwait(false);
return;
case Socks5Protocol.MethodNoneAcceptable:
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthMethodRejected,
"The SOCKS5 server rejected all offered authentication methods.",
_options.ToString());
default:
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"The SOCKS5 server selected an unsupported authentication method: 0x{chosenMethod:X2}.",
_options.ToString());
}
}
async Task PerformUsernamePasswordAuthAsync(NetworkStream stream, CancellationToken cancellationToken)
{
var usernameBytes = Encoding.UTF8.GetBytes(_options.Username ?? string.Empty);
var passwordBytes = _options.Password ?? [];
if (usernameBytes.Length > Socks5Protocol.MaxStringLength)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthFailed,
$"Username exceeds {Socks5Protocol.MaxStringLength} bytes (RFC 1929).",
_options.ToString());
}
if (usernameBytes.Length == 0)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthFailed,
"Username must be 1..255 bytes (RFC 1929).",
_options.ToString());
}
if (passwordBytes.Length > Socks5Protocol.MaxStringLength)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthFailed,
$"Password exceeds {Socks5Protocol.MaxStringLength} bytes (RFC 1929).",
_options.ToString());
}
if (passwordBytes.Length == 0)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthFailed,
"Password must be 1..255 bytes (RFC 1929).",
_options.ToString());
}
var request = new byte[3 + usernameBytes.Length + passwordBytes.Length];
var offset = 0;
request[offset++] = Socks5Protocol.AuthSubNegotiationVersion;
request[offset++] = (byte)usernameBytes.Length;
Buffer.BlockCopy(usernameBytes, 0, request, offset, usernameBytes.Length);
offset += usernameBytes.Length;
request[offset++] = (byte)passwordBytes.Length;
Buffer.BlockCopy(passwordBytes, 0, request, offset, passwordBytes.Length);
await stream.WriteAsync(request, cancellationToken).ConfigureAwait(false);
var response = new byte[2];
await ReadExactlyAsync(stream, response, cancellationToken).ConfigureAwait(false);
if (response[0] != Socks5Protocol.AuthSubNegotiationVersion)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Unexpected SOCKS5 auth sub-negotiation version: 0x{response[0]:X2}.",
_options.ToString());
}
if (response[1] != 0x00)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAuthFailed,
$"The SOCKS5 server rejected the supplied credentials (status 0x{response[1]:X2}).",
_options.ToString());
}
}
async Task PerformConnectAsync(NetworkStream stream, EndPoint brokerEndPoint, CancellationToken cancellationToken)
{
var request = BuildConnectRequest(brokerEndPoint, out var targetDescription);
await stream.WriteAsync(request, cancellationToken).ConfigureAwait(false);
// Reply: VER REP RSV ATYP BND.ADDR BND.PORT
var header = new byte[4];
await ReadExactlyAsync(stream, header, cancellationToken).ConfigureAwait(false);
if (header[0] != Socks5Protocol.Version)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Unexpected SOCKS version in CONNECT response: 0x{header[0]:X2}.",
_options.ToString());
}
if (header[2] != Socks5Protocol.Reserved)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Unexpected SOCKS5 reserved byte in CONNECT response: 0x{header[2]:X2}.",
_options.ToString());
}
var rep = header[1];
if (rep != Socks5Protocol.ReplySucceeded)
{
var (code, description) = MapReplyCode(rep);
throw new MqttProxyException(
code,
$"SOCKS5 CONNECT to '{targetDescription}' failed: {description}.",
_options.ToString());
}
// Discard BND.ADDR / BND.PORT. The length depends on ATYP and we tolerate unexpected lengths.
var atyp = header[3];
switch (atyp)
{
case Socks5Protocol.AddressTypeIPv4:
await SkipAsync(stream, 4 + 2, cancellationToken).ConfigureAwait(false);
break;
case Socks5Protocol.AddressTypeIPv6:
await SkipAsync(stream, 16 + 2, cancellationToken).ConfigureAwait(false);
break;
case Socks5Protocol.AddressTypeDomainName:
var lenBuf = new byte[1];
await ReadExactlyAsync(stream, lenBuf, cancellationToken).ConfigureAwait(false);
await SkipAsync(stream, lenBuf[0] + 2, cancellationToken).ConfigureAwait(false);
break;
default:
throw new MqttProxyException(
MqttProxyErrorCode.ProxyProtocolError,
$"Unexpected SOCKS5 address type in CONNECT response: 0x{atyp:X2}.",
_options.ToString());
}
}
byte[] BuildConnectRequest(EndPoint brokerEndPoint, out string targetDescription)
{
// Layout: VER CMD RSV ATYP DST.ADDR DST.PORT
if (brokerEndPoint is DnsEndPoint dns)
{
if (_options.ResolveDnsRemotely)
{
return BuildDomainConnectRequest(dns.Host, dns.Port, out targetDescription);
}
var addresses = ResolveDns(dns.Host, dns.Port);
return BuildIpConnectRequest(addresses, dns.Port, out targetDescription);
}
if (brokerEndPoint is IPEndPoint ip)
{
return BuildIpConnectRequest([ip.Address], ip.Port, out targetDescription);
}
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAddressTypeNotSupported,
$"Broker endpoint of type '{brokerEndPoint.GetType().Name}' is not supported by the SOCKS5 stream provider.",
_options.ToString());
}
IPAddress[] ResolveDns(string host, int port)
{
try
{
var addresses = _options.AddressFamily == AddressFamily.Unspecified
? Dns.GetHostAddresses(host)
: Dns.GetHostAddresses(host, _options.AddressFamily);
if (addresses == null || addresses.Length == 0)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyHostUnreachable,
$"DNS resolution for '{host}:{port}' returned no addresses.",
_options.ToString());
}
return addresses;
}
catch (SocketException ex)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyHostUnreachable,
$"Local DNS resolution failed for '{host}:{port}': {ex.Message}",
_options.ToString(),
ex);
}
}
byte[] BuildIpConnectRequest(IPAddress[] addresses, int port, out string targetDescription)
{
var preferredFamily = _options.AddressFamily == AddressFamily.Unspecified
? AddressFamily.InterNetwork
: _options.AddressFamily;
IPAddress chosen = null;
foreach (var address in addresses)
{
if (address.AddressFamily == preferredFamily)
{
chosen = address;
break;
}
}
chosen ??= addresses[0];
targetDescription = $"{chosen}:{port}";
if (chosen.AddressFamily == AddressFamily.InterNetwork)
{
var addr = chosen.GetAddressBytes();
var request = new byte[4 + 4 + 2];
request[0] = Socks5Protocol.Version;
request[1] = Socks5Protocol.CommandConnect;
request[2] = Socks5Protocol.Reserved;
request[3] = Socks5Protocol.AddressTypeIPv4;
Buffer.BlockCopy(addr, 0, request, 4, 4);
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(8, 2), (ushort)port);
return request;
}
if (chosen.AddressFamily == AddressFamily.InterNetworkV6)
{
var addr = chosen.GetAddressBytes();
var request = new byte[4 + 16 + 2];
request[0] = Socks5Protocol.Version;
request[1] = Socks5Protocol.CommandConnect;
request[2] = Socks5Protocol.Reserved;
request[3] = Socks5Protocol.AddressTypeIPv6;
Buffer.BlockCopy(addr, 0, request, 4, 16);
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(20, 2), (ushort)port);
return request;
}
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAddressTypeNotSupported,
$"IP address family '{chosen.AddressFamily}' is not supported by SOCKS5.",
_options.ToString());
}
byte[] BuildDomainConnectRequest(string host, int port, out string targetDescription)
{
var hostBytes = Encoding.UTF8.GetBytes(host ?? string.Empty);
if (hostBytes.Length == 0)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyHostUnreachable,
"Broker host is empty.",
_options.ToString());
}
if (hostBytes.Length > Socks5Protocol.MaxStringLength)
{
throw new MqttProxyException(
MqttProxyErrorCode.ProxyAddressTypeNotSupported,
$"Broker host exceeds the SOCKS5 DOMAINNAME limit of {Socks5Protocol.MaxStringLength} bytes.",
_options.ToString());
}
targetDescription = $"{host}:{port}";
var request = new byte[4 + 1 + hostBytes.Length + 2];
request[0] = Socks5Protocol.Version;
request[1] = Socks5Protocol.CommandConnect;
request[2] = Socks5Protocol.Reserved;
request[3] = Socks5Protocol.AddressTypeDomainName;
request[4] = (byte)hostBytes.Length;
Buffer.BlockCopy(hostBytes, 0, request, 5, hostBytes.Length);
BinaryPrimitives.WriteUInt16BigEndian(request.AsSpan(5 + hostBytes.Length, 2), (ushort)port);
return request;
}
static (MqttProxyErrorCode Code, string Description) MapReplyCode(byte rep)
{
return rep switch
{
Socks5Protocol.ReplyGeneralFailure => (MqttProxyErrorCode.ProxyGeneralFailure, "general SOCKS server failure"),
Socks5Protocol.ReplyConnectionNotAllowed => (MqttProxyErrorCode.ProxyConnectionNotAllowed, "connection not allowed by ruleset"),
Socks5Protocol.ReplyNetworkUnreachable => (MqttProxyErrorCode.ProxyNetworkUnreachable, "network unreachable"),
Socks5Protocol.ReplyHostUnreachable => (MqttProxyErrorCode.ProxyHostUnreachable, "host unreachable"),
Socks5Protocol.ReplyConnectionRefused => (MqttProxyErrorCode.ProxyConnectionRefused, "connection refused"),
Socks5Protocol.ReplyTtlExpired => (MqttProxyErrorCode.ProxyTtlExpired, "TTL expired"),
Socks5Protocol.ReplyCommandNotSupported => (MqttProxyErrorCode.ProxyCommandNotSupported, "command not supported"),
Socks5Protocol.ReplyAddressTypeNotSupported => (MqttProxyErrorCode.ProxyAddressTypeNotSupported, "address type not supported"),
_ => (MqttProxyErrorCode.ProxyUnknownError, $"unknown reply code 0x{rep:X2}")
};
}
static async Task ReadExactlyAsync(NetworkStream stream, byte[] buffer, CancellationToken cancellationToken)
{
var offset = 0;
while (offset < buffer.Length)
{
var read = await stream.ReadAsync(buffer.AsMemory(offset, buffer.Length - offset), cancellationToken).ConfigureAwait(false);
if (read <= 0)
{
throw new EndOfStreamException();
}
offset += read;
}
}
static async Task SkipAsync(NetworkStream stream, int count, CancellationToken cancellationToken)
{
if (count <= 0)
{
return;
}
var buffer = new byte[count];
await ReadExactlyAsync(stream, buffer, cancellationToken).ConfigureAwait(false);
}
}
@@ -1,53 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>MQTTnet.Extensions.TopicTemplate</AssemblyName>
<RootNamespace>MQTTnet.Extensions.TopicTemplate</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Company>The contributors of MQTTnet</Company>
<Product>MQTTnet</Product>
<Description>
Provides mqtt topic templating logic to support dispatch,
routing and similar functionality based on the well known moustache syntax (AsyncAPI compatible).
</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Authors>The contributors of MQTTnet</Authors>
<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>
<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>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Include="README.md">
<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" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup>
<ItemGroup>
@@ -2,11 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using MQTTnet.Protocol;
namespace MQTTnet.Extensions.TopicTemplate;
@@ -84,13 +80,13 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
{
var filter = TopicFilter;
// 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;
}
// append hash if neccessary
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard.ToString()))
if (!filter.EndsWith(MqttTopicFilterComparer.MultiLevelWildcard))
{
filter += MqttTopicFilterComparer.MultiLevelWildcard;
}
@@ -161,12 +157,12 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
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);
}
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString()))
if (root.EndsWith(MqttTopicFilterComparer.LevelSeparator.ToString(), StringComparison.InvariantCulture))
{
root += MqttTopicFilterComparer.SingleLevelWildcard;
}
@@ -230,7 +226,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
throw new ArgumentException("the topic has to match this template", nameof(topic));
}
return parseParameterValuesInternal(topic);
return ParseParameterValuesInternal(topic);
}
/// <summary>
@@ -330,7 +326,7 @@ public sealed class MqttTopicTemplate : IEquatable<MqttTopicTemplate>
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
var segments = topic.Split(MqttTopicFilterComparer.LevelSeparator);
@@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using MQTTnet.Packets;
using MQTTnet.Protocol;
@@ -5,28 +5,27 @@
using MQTTnet.Packets;
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>
/// The reason string is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// The reason string is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString { get; set; }
/// <summary>
/// The server reference is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; set; }
/// <summary>
/// The server reference is sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ServerReference { get; set; }
/// <summary>
/// These user properties are sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
/// <summary>
/// These user properties are sent to every client via a DISCONNECT packet.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties { get; set; }
}
@@ -5,50 +5,72 @@
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
namespace MQTTnet.Server;
public sealed class MqttServerClientDisconnectOptionsBuilder
{
public sealed class MqttServerClientDisconnectOptionsBuilder
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
public MqttServerClientDisconnectOptions Build()
{
readonly MqttServerClientDisconnectOptions _options = new MqttServerClientDisconnectOptions();
return _options;
}
public MqttServerClientDisconnectOptions Build()
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
{
_options.ReasonCode = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
{
_options.ReasonString = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
{
_options.ServerReference = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
{
_options.UserProperties = value;
return this;
}
[Obsolete("Please use more performance `WithUserProperty` with ArraySegment<byte> or ReadOnlyMemory<byte> for the value.")]
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{
return _options;
_options.UserProperties = new List<MqttUserProperty>();
}
public MqttServerClientDisconnectOptionsBuilder WithReasonCode(MqttDisconnectReasonCode value)
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, ReadOnlyMemory<byte> value)
{
if (_options.UserProperties == null)
{
_options.ReasonCode = value;
return this;
_options.UserProperties = new List<MqttUserProperty>();
}
public MqttServerClientDisconnectOptionsBuilder WithReasonString(string value)
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, ArraySegment<byte> value)
{
if (_options.UserProperties == null)
{
_options.ReasonString = value;
return this;
_options.UserProperties = new List<MqttUserProperty>();
}
public MqttServerClientDisconnectOptionsBuilder WithServerReference(string value)
{
_options.ServerReference = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperties(List<MqttUserProperty> value)
{
_options.UserProperties = value;
return this;
}
public MqttServerClientDisconnectOptionsBuilder WithUserProperty(string name, string value)
{
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
}
@@ -51,13 +51,39 @@ public sealed class ExchangeEnhancedAuthenticationOptionsFactory
return this;
}
[Obsolete("Please use more performance `WithUserProperty` with ArraySegment<byte> or ReadOnlyMemory<byte> for the value.")]
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, string value)
{
if (name == null)
ArgumentNullException.ThrowIfNull(name);
if (_options.UserProperties == null)
{
throw new ArgumentNullException(nameof(name));
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, ReadOnlyMemory<byte> value)
{
ArgumentNullException.ThrowIfNull(name);
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
}
_options.UserProperties.Add(new MqttUserProperty(name, value));
return this;
}
public ExchangeEnhancedAuthenticationOptionsFactory WithUserProperty(string name, ArraySegment<byte> value)
{
ArgumentNullException.ThrowIfNull(name);
if (_options.UserProperties == null)
{
_options.UserProperties = new List<MqttUserProperty>();
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server;
namespace MQTTnet.Server
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
{
public sealed class ApplicationMessageEnqueuedEventArgs : EventArgs
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
{
public ApplicationMessageEnqueuedEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, bool isDropped)
{
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
IsDropped = isDropped;
}
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
SenderClientId = senderClientId ?? throw new ArgumentNullException( nameof(senderClientId));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
IsDropped = isDropped;
}
public string SenderClientId { get; }
public string ReceiverClientId { get; }
public bool IsDropped { get; }
public MqttApplicationMessage ApplicationMessage { get; }
}
@@ -2,26 +2,23 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server;
namespace MQTTnet.Server
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
{
public sealed class ApplicationMessageNotConsumedEventArgs : EventArgs
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{
public ApplicationMessageNotConsumedEventArgs(MqttApplicationMessage applicationMessage, string senderId)
{
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
SenderId = senderId;
}
/// <summary>
/// Gets the application message which was not consumed by any client.
/// </summary>
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Gets the ID of the client which has sent the affected application message.
/// </summary>
public string SenderId { get; }
}
@@ -2,51 +2,49 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using 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, string userName, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
}
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
}
/// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
/// </summary>
public bool IsCompleted => AcknowledgePacket is MqttPubAckPacket || AcknowledgePacket is MqttPubCompPacket;
/// <summary>
/// Gets the PUBLISH packet which was acknowledged.
/// </summary>
public MqttPublishPacket PublishPacket { get; }
/// <summary>
/// Gets the session items which contain custom user data per session.
/// </summary>
public IDictionary SessionItems { get; }
}
@@ -2,70 +2,67 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using MQTTnet.Formatter;
using MQTTnet.Packets;
namespace MQTTnet.Server
namespace MQTTnet.Server;
public sealed class ClientConnectedEventArgs : EventArgs
{
public sealed class ClientConnectedEventArgs : EventArgs
readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
{
readonly MqttConnectPacket _connectPacket;
public ClientConnectedEventArgs(MqttConnectPacket connectPacket, MqttProtocolVersion protocolVersion, EndPoint remoteEndPoint, IDictionary sessionItems)
{
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the connected client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
ProtocolVersion = protocolVersion;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
public byte[] AuthenticationData => _connectPacket.AuthenticationData;
public string AuthenticationMethod => _connectPacket.AuthenticationMethod;
/// <summary>
/// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the connected client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
/// <summary>
/// Gets the user properties sent by the client.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.UserProperties;
}
@@ -2,91 +2,88 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server
namespace MQTTnet.Server;
public sealed class ClientDisconnectedEventArgs : EventArgs
{
public sealed class ClientDisconnectedEventArgs : EventArgs
readonly MqttConnectPacket _connectPacket;
readonly MqttDisconnectPacket _disconnectPacket;
public ClientDisconnectedEventArgs(
MqttConnectPacket connectPacket,
MqttDisconnectPacket disconnectPacket,
MqttClientDisconnectType disconnectType,
EndPoint remoteEndPoint,
IDictionary sessionItems)
{
readonly MqttConnectPacket _connectPacket;
readonly MqttDisconnectPacket _disconnectPacket;
DisconnectType = disconnectType;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
public ClientDisconnectedEventArgs(
MqttConnectPacket connectPacket,
MqttDisconnectPacket disconnectPacket,
MqttClientDisconnectType disconnectType,
EndPoint remoteEndPoint,
IDictionary sessionItems)
{
DisconnectType = disconnectType;
RemoteEndPoint = remoteEndPoint;
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
_connectPacket = connectPacket;
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
_disconnectPacket = disconnectPacket;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
public MqttClientDisconnectType DisconnectType { get; }
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
_connectPacket = connectPacket;
// The DISCONNECT packet can be null in case of a non clean disconnect or session takeover.
_disconnectPacket = disconnectPacket;
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId => _connectPacket.ClientId;
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName => _connectPacket.Username;
/// <summary>
/// Gets the password of the client.
/// </summary>
public string Password => Encoding.UTF8.GetString(_connectPacket.Password.AsSpan());
public MqttClientDisconnectType DisconnectType { get; }
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets the reason code sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public MqttDisconnectReasonCode? ReasonCode => _disconnectPacket?.ReasonCode;
/// <summary>
/// Gets the reason string sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public string ReasonString => _disconnectPacket?.ReasonString;
/// <summary>
/// Gets the session expiry interval sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public uint SessionExpiryInterval => _disconnectPacket?.SessionExpiryInterval ?? 0;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the user properties sent by the client.
/// Only available for clean disconnects.
/// <remarks>MQTT 5.0.0+ feature.</remarks>
/// </summary>
public List<MqttUserProperty> UserProperties => _disconnectPacket?.UserProperties;
}
@@ -2,42 +2,40 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using 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, string userName, MqttTopicFilter topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public MqttTopicFilter TopicFilter { get; }
}
@@ -2,41 +2,39 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
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 userName, string topicFilter, IDictionary sessionItems)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public string TopicFilter { get; }
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
}
/// <summary>
/// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
/// <summary>
/// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards.
/// </summary>
public string TopicFilter { get; }
}
@@ -2,34 +2,31 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Server;
namespace MQTTnet.Server
public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs
{
public sealed class InterceptingClientApplicationMessageEnqueueEventArgs : EventArgs
public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage)
{
public InterceptingClientApplicationMessageEnqueueEventArgs(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage)
{
SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
/// <summary>
/// Gets or sets whether the enqueue of the application message should be performed or not.
/// If set to _False_ the client will not receive the application message.
/// </summary>
public bool AcceptEnqueue { get; set; } = true;
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Indicates if the connection with the sender should be closed.
/// </summary>
public bool CloseSenderConnection { get; set; }
public string ReceiverClientId { get; }
public string SenderClientId { get; }
SenderClientId = senderClientId ?? throw new ArgumentNullException(nameof(senderClientId));
ReceiverClientId = receiverClientId ?? throw new ArgumentNullException(nameof(receiverClientId));
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
}
/// <summary>
/// Gets or sets whether the enqueue of the application message should be performed or not.
/// If set to _False_ the client will not receive the application message.
/// </summary>
public bool AcceptEnqueue { get; set; } = true;
public MqttApplicationMessage ApplicationMessage { get; }
/// <summary>
/// Indicates if the connection with the sender should be closed.
/// </summary>
public bool CloseSenderConnection { get; set; }
public string ReceiverClientId { get; }
public string SenderClientId { get; }
}
@@ -2,63 +2,60 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Net;
using System.Threading;
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 userName, EndPoint remoteEndPoint, MqttPacket packet, IDictionary sessionItems)
{
CancellationToken = cancellationToken;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
RemoteEndPoint = remoteEndPoint;
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
SessionItems = sessionItems;
}
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client ID which has sent the packet or will receive the packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets the endpoint of the sending or receiving client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets or sets the MQTT packet which was received or will be sent.
/// </summary>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
CancellationToken = cancellationToken;
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
UserName = userName;
RemoteEndPoint = remoteEndPoint;
Packet = packet ?? throw new ArgumentNullException(nameof(packet));
SessionItems = sessionItems;
}
/// <summary>
/// Gets the cancellation token from the connection managing thread.
/// Use this in further event processing.
/// </summary>
public CancellationToken CancellationToken { get; }
/// <summary>
/// Gets the client ID which has sent the packet or will receive the packet.
/// </summary>
public string ClientId { get; }
/// <summary>
/// Gets the user name of the client.
/// </summary>
public string UserName { get; }
/// <summary>
/// Gets the endpoint of the sending or receiving client.
/// </summary>
public EndPoint RemoteEndPoint { get; }
[Obsolete("Use RemoteEndPoint instead.")]
public string Endpoint => RemoteEndPoint?.ToString();
/// <summary>
/// Gets or sets the MQTT packet which was received or will be sent.
/// </summary>
public MqttPacket Packet { get; set; }
/// <summary>
/// Gets or sets whether the packet should be processed or not.
/// </summary>
public bool ProcessPacket { get; set; } = true;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary SessionItems { get; }
}

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