mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-16 17:23:04 +08:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79649d71b | ||
|
|
c9c27b9f01 | ||
|
|
0bc47cf853 | ||
|
|
5649328bc5 | ||
|
|
726dd15c5a | ||
|
|
d513c6f85c | ||
|
|
c1c17ba162 | ||
|
|
f8aa686557 | ||
|
|
d7eb4bbe6e | ||
|
|
c17a350796 | ||
|
|
bb106f8c6e | ||
|
|
09e47fb5eb | ||
|
|
099209fff2 | ||
|
|
d572490a67 | ||
|
|
180e7f79d4 | ||
|
|
d9da7cc92e | ||
|
|
c2ce2044cc | ||
|
|
e400bfb79e | ||
|
|
3d32faa8f9 | ||
|
|
862d76471b | ||
|
|
27f2474876 | ||
|
|
01a5aeeb58 | ||
|
|
47df131e7f | ||
|
|
56750adfe4 | ||
|
|
11e64ef018 | ||
|
|
9b20e0a430 | ||
|
|
41df2ec5da | ||
|
|
e45b207375 | ||
|
|
c37866e534 | ||
|
|
bda4ab7025 | ||
|
|
32cc1ff7b1 | ||
|
|
b5952a590a | ||
|
|
581a024ceb | ||
|
|
c45cb8b04e | ||
|
|
a5e1a4d6dd |
@@ -1 +1,9 @@
|
|||||||
|
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger).
|
||||||
|
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593).
|
||||||
|
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang).
|
||||||
|
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun).
|
||||||
|
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang).
|
||||||
|
* [Server] Improved memory usage for ASP.NET connections (#1582, thanks to @xljiulang).
|
||||||
|
* [Server] Improved memory usage and performance for ASP.NET integration (#1596, thanks to @xljiulang).
|
||||||
|
* [Server] Add support for interception of will messages (#1613).
|
||||||
|
* [Server] Fixed wrong topic filter detections (#1615, thanks to @logicaloud).
|
||||||
@@ -3,7 +3,7 @@ name: CI
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: "4.1.3.${{github.run_number}}"
|
VERSION: "4.1.4.${{github.run_number}}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -60,8 +60,4 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish MyGet nugets
|
- name: Publish MyGet nugets
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
run: dotnet nuget push **/*.nupkg -k ${{ secrets.MYGET_API_KEY }} -s https://www.myget.org/F/mqttnet/api/v3/index.json --skip-duplicate
|
||||||
|
|
||||||
# - name: Publish nugets
|
|
||||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
|
||||||
# run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate
|
|
||||||
@@ -240,8 +240,14 @@ See the LICENSE file in the project root for more information.</s:String>
|
|||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=CONNACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGREQ/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PINGRESP/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBCOMP/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREC/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=PUBREL/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=SUBACK/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tnet/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://www.nuget.org/packages/MQTTnet)
|
[](https://www.nuget.org/packages/MQTTnet)
|
||||||
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
[](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
|
||||||
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
[](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
|
||||||

|

|
||||||
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public static class Client_Connection_Samples
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||||
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
|
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
|
||||||
* encryption algorithms and supports more platforms.
|
* encryption algorithms and supports more platforms.
|
||||||
*
|
*
|
||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||||
@@ -386,4 +386,4 @@ public static class Client_Connection_Samples
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,10 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
|
|
||||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
|
|||||||
@@ -12,8 +12,6 @@
|
|||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Tests.Extensions;
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests
|
namespace MQTTnet.AspNetCore.Tests
|
||||||
{
|
{
|
||||||
@@ -35,7 +35,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
connection.Transport = pipe;
|
connection.Transport = pipe;
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
var ctx = new MqttConnectionContext(serializer, connection);
|
||||||
|
|
||||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
|
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
|
||||||
|
|
||||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||||
|
|
||||||
|
|||||||
@@ -8,17 +8,15 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="wwwroot\"/>
|
<Folder Include="wwwroot\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,38 +2,59 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
using MQTTnet.AspNetCore.Client.Tcp;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||||
{
|
{
|
||||||
|
readonly ConnectionContext _connection;
|
||||||
readonly AsyncLock _writerLock = new AsyncLock();
|
readonly AsyncLock _writerLock = new AsyncLock();
|
||||||
|
|
||||||
PipeReader _input;
|
PipeReader _input;
|
||||||
PipeWriter _output;
|
PipeWriter _output;
|
||||||
|
|
||||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||||
{
|
{
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
if (Connection.Transport != null)
|
_input = connection.Transport.Input;
|
||||||
|
_output = connection.Transport.Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long BytesReceived { get; private set; }
|
||||||
|
|
||||||
|
public long BytesSent { get; private set; }
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
_input = Connection.Transport.Input;
|
// mqtt over tcp
|
||||||
_output = Connection.Transport.Output;
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return tlsFeature.ClientCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,47 +62,59 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if NETCOREAPP3_1
|
// mqtt over tcp
|
||||||
if (Connection?.RemoteEndPoint != null)
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
|
if (_connection.RemoteEndPoint != null)
|
||||||
{
|
{
|
||||||
return Connection.RemoteEndPoint.ToString();
|
return _connection.RemoteEndPoint.ToString();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var connection = Http?.HttpContext?.Connection;
|
// mqtt over websocket
|
||||||
if (connection == null)
|
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||||
|
if (httpFeature?.RemoteIpAddress != null)
|
||||||
{
|
{
|
||||||
return Connection.ConnectionId;
|
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
|
||||||
|
|
||||||
public ConnectionContext Connection { get; }
|
|
||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
|
||||||
|
|
||||||
public long BytesSent { get; set; }
|
|
||||||
|
|
||||||
public long BytesReceived { get; set; }
|
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
public bool IsReadingPacket { get; private set; }
|
||||||
|
|
||||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
public bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
if (httpFeature?.HttpContext != null)
|
||||||
|
{
|
||||||
|
return httpFeature.HttpContext.Request.IsHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
if (_connection is TcpConnection tcp && !tcp.IsConnected)
|
||||||
{
|
{
|
||||||
await tcp.StartAsync().ConfigureAwait(false);
|
await tcp.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_input = Connection.Transport.Input;
|
_input = _connection.Transport.Input;
|
||||||
_output = Connection.Transport.Output;
|
_output = _connection.Transport.Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||||
@@ -92,16 +125,19 @@ namespace MQTTnet.AspNetCore
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_writerLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var input = Connection.Transport.Input;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
ReadResult readResult;
|
ReadResult readResult;
|
||||||
var readTask = input.ReadAsync(cancellationToken);
|
var readTask = _input.ReadAsync(cancellationToken);
|
||||||
if (readTask.IsCompleted)
|
if (readTask.IsCompleted)
|
||||||
{
|
{
|
||||||
readResult = readTask.Result;
|
readResult = readTask.Result;
|
||||||
@@ -141,15 +177,15 @@ namespace MQTTnet.AspNetCore
|
|||||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
// 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
|
// 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.
|
// before yielding the read again.
|
||||||
input.AdvanceTo(consumed, observed);
|
_input.AdvanceTo(consumed, observed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
// completing the channel makes sure that there is no more data read after a protocol error
|
// completing the channel makes sure that there is no more data read after a protocol error
|
||||||
_input?.Complete(e);
|
_input?.Complete(exception);
|
||||||
_output?.Complete(e);
|
_output?.Complete(exception);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -169,25 +205,35 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var formatter = PacketFormatterAdapter;
|
|
||||||
|
|
||||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var buffer = formatter.Encode(packet);
|
try
|
||||||
var msg = buffer.Join().AsMemory();
|
|
||||||
var output = _output;
|
|
||||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
{
|
||||||
BytesSent += msg.Length;
|
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||||
}
|
|
||||||
|
|
||||||
PacketFormatterAdapter.Cleanup();
|
WritePacketBuffer(_output, buffer);
|
||||||
|
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
BytesSent += buffer.Length;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PacketFormatterAdapter.Cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
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.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||||
|
|
||||||
|
output.Advance(buffer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,21 +2,23 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public static class ReaderExtensions
|
public static class ReaderExtensions
|
||||||
{
|
{
|
||||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||||
in ReadOnlySequence<byte> input,
|
in ReadOnlySequence<byte> input,
|
||||||
out MqttPacket packet,
|
out MqttPacket packet,
|
||||||
out SequencePosition consumed,
|
out SequencePosition consumed,
|
||||||
out SequencePosition observed,
|
out SequencePosition observed,
|
||||||
out int bytesRead)
|
out int bytesRead)
|
||||||
{
|
{
|
||||||
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fixedHeader = copy.First.Span[0];
|
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
copy = copy.Slice(headerLength);
|
||||||
if (copy.Length < bodyLength)
|
if (copy.Length < bodyLength)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodySlice = copy.Slice(0, bodyLength);
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
var buffer = bodySlice.GetMemory().ToArray();
|
var bodySegment = GetArraySegment(ref bodySlice);
|
||||||
|
|
||||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||||
|
|
||||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
{
|
{
|
||||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
|
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||||
{
|
{
|
||||||
if (input.IsSingleSegment)
|
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||||
{
|
{
|
||||||
return input.First;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be rare
|
// Should be rare
|
||||||
return input.ToArray();
|
var array = input.ToArray();
|
||||||
|
return new ArraySegment<byte>(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||||
|
{
|
||||||
|
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
|
||||||
|
if (valueSequence.IsSingleSegment)
|
||||||
|
{
|
||||||
|
var valueSpan =
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
valueSequence.FirstSpan;
|
||||||
|
#else
|
||||||
|
valueSequence.First.Span;
|
||||||
|
#endif
|
||||||
|
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<byte> valueSpan = stackalloc byte[8];
|
||||||
|
valueSequence.CopyTo(valueSpan);
|
||||||
|
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
|
||||||
|
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadBodyLength(ReadOnlySpan<byte> span, 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.
|
// 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 multiplier = 1;
|
||||||
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
|
|||||||
var index = 1;
|
var index = 1;
|
||||||
headerLength = 0;
|
headerLength = 0;
|
||||||
bodyLength = 0;
|
bodyLength = 0;
|
||||||
|
|
||||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (index == temp.Length)
|
if (index == span.Length)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedByte = temp[index];
|
encodedByte = span[index];
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
value += (byte)(encodedByte & 127) * multiplier;
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
if (multiplier > 128 * 128 * 128)
|
if (multiplier > 128 * 128 * 128)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
ThrowProtocolViolationException(span, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplier *= 128;
|
multiplier *= 128;
|
||||||
} while ((encodedByte & 128) != 0);
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
input = input.Slice(index);
|
|
||||||
|
|
||||||
headerLength = index;
|
headerLength = index;
|
||||||
bodyLength = value;
|
bodyLength = value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// 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.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public abstract class BaseBenchmark
|
public abstract class BaseBenchmark
|
||||||
|
|||||||
@@ -2,54 +2,25 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class ChannelAdapterBenchmark
|
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttChannelAdapter _channelAdapter;
|
MqttChannelAdapter _channelAdapter;
|
||||||
int _iterations;
|
int _iterations;
|
||||||
MemoryStream _stream;
|
|
||||||
MqttPublishPacket _packet;
|
MqttPublishPacket _packet;
|
||||||
|
MemoryStream _stream;
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_packet = new MqttPublishPacket
|
|
||||||
{
|
|
||||||
Topic = "A"
|
|
||||||
};
|
|
||||||
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
|
|
||||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
|
||||||
|
|
||||||
_iterations = 10000;
|
|
||||||
|
|
||||||
_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, null, new MqttNetEventLogger());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Receive_10000_Messages()
|
public void Receive_10000_Messages()
|
||||||
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
|
|||||||
_stream.Position = 0;
|
_stream.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_packet = new MqttPublishPacket
|
||||||
|
{
|
||||||
|
Topic = "A"
|
||||||
|
};
|
||||||
|
|
||||||
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
|
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||||
|
|
||||||
|
_iterations = 10000;
|
||||||
|
|
||||||
|
_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, null, new MqttNetEventLogger());
|
||||||
|
}
|
||||||
|
|
||||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
{
|
{
|
||||||
var buffer = new MemoryStream();
|
var buffer = new MemoryStream();
|
||||||
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
return buffer.ToArray();
|
return buffer.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Validators;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
|
|
||||||
/// https://benchmarkdotnet.org/Configs/Configs.htm
|
|
||||||
/// </summary>
|
|
||||||
public class AllowNonOptimized : BaseConfig
|
|
||||||
{
|
|
||||||
public AllowNonOptimized()
|
|
||||||
{
|
|
||||||
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
|
|
||||||
AddJob(Job.InProcess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Configs;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class BaseConfig : ManualConfig
|
|
||||||
{
|
|
||||||
public BaseConfig()
|
|
||||||
{
|
|
||||||
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
|
|
||||||
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
|
|
||||||
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Environments;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Toolchains.CsProj;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class RuntimeCompareConfig : BaseConfig
|
|
||||||
{
|
|
||||||
public RuntimeCompareConfig()
|
|
||||||
{
|
|
||||||
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
|
|
||||||
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class LoggerBenchmark
|
public class LoggerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttNetNullLogger _nullLogger;
|
MqttNetNullLogger _nullLogger;
|
||||||
MqttNetSourceLogger _sourceNullLogger;
|
MqttNetSourceLogger _sourceNullLogger;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<DebugType>Full</DebugType>
|
<DebugType>Full</DebugType>
|
||||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>7.2</LangVersion>
|
<LangVersion>7.2</LangVersion>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -26,9 +28,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MemoryCopyBenchmark
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
using System;
|
||||||
@@ -8,14 +11,10 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Create a number of topics, publish, subscribe, and wait for response
|
|
||||||
/// </summary>
|
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageDeliveryBenchmark
|
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
List<MqttApplicationMessage> _topicPublishMessages;
|
List<MqttApplicationMessage> _topicPublishMessages;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingBenchmark
|
public class MessageProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingMqttConnectionContextBenchmark
|
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IWebHost _host;
|
IWebHost _host;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MqttBufferReaderBenchmark
|
||||||
|
{
|
||||||
|
byte[] _buffer;
|
||||||
|
int _bufferLength;
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.AspNetCore;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MqttPacketReaderWriterBenchmark
|
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
readonly byte[] _demoPayload = new byte[1024];
|
readonly byte[] _demoPayload = new byte[1024];
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class MqttTcpChannelBenchmark
|
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttChannel _serverChannel;
|
IMqttChannel _serverChannel;
|
||||||
|
|||||||
@@ -3,85 +3,140 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Loggers;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using MQTTnet.Benchmarks.Configurations;
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
static int _selectedBenchmarkIndex;
|
||||||
{
|
static List<Type> _benchmarks;
|
||||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
|
||||||
Console.WriteLine("--------------------------------------------------------");
|
|
||||||
Console.WriteLine("1 = MessageProcessingBenchmark");
|
|
||||||
Console.WriteLine("2 = SerializerBenchmark");
|
|
||||||
Console.WriteLine("3 = LoggerBenchmark");
|
|
||||||
Console.WriteLine("4 = TopicFilterComparerBenchmark");
|
|
||||||
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
|
||||||
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
|
||||||
Console.WriteLine("7 = TcpPipesBenchmark");
|
|
||||||
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
|
|
||||||
Console.WriteLine("9 = ServerProcessingBenchmark");
|
|
||||||
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
|
|
||||||
Console.WriteLine("b = RoundtripBenchmark");
|
|
||||||
Console.WriteLine("c = SubscribeBenchmark");
|
|
||||||
Console.WriteLine("d = UnsubscribeBenchmark");
|
|
||||||
Console.WriteLine("e = MessageDeliveryBenchmark");
|
|
||||||
Console.WriteLine("f = AsyncLockBenchmark");
|
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
public static void Main(string[] arguments)
|
||||||
switch (pressedKey.KeyChar)
|
{
|
||||||
|
_benchmarks = CollectBenchmarks();
|
||||||
|
HandleArguments(arguments);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
case '1':
|
RenderMenu();
|
||||||
BenchmarkRunner.Run<MessageProcessingBenchmark>();
|
|
||||||
break;
|
var key = Console.ReadKey(true);
|
||||||
case '2':
|
|
||||||
BenchmarkRunner.Run<SerializerBenchmark>();
|
if (key.Key == ConsoleKey.DownArrow)
|
||||||
break;
|
{
|
||||||
case '3':
|
_selectedBenchmarkIndex++;
|
||||||
BenchmarkRunner.Run<LoggerBenchmark>();
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
break;
|
{
|
||||||
case '4':
|
_selectedBenchmarkIndex = 0;
|
||||||
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
|
}
|
||||||
break;
|
}
|
||||||
case '5':
|
else if (key.Key == ConsoleKey.UpArrow)
|
||||||
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex--;
|
||||||
case '6':
|
if (_selectedBenchmarkIndex < 0)
|
||||||
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
case '7':
|
}
|
||||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
}
|
||||||
break;
|
else if (key.Key == ConsoleKey.Escape)
|
||||||
case '8':
|
{
|
||||||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
|
Environment.Exit(0);
|
||||||
break;
|
}
|
||||||
case '9':
|
else if (key.Key == ConsoleKey.Enter)
|
||||||
BenchmarkRunner.Run<ServerProcessingBenchmark>();
|
{
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
BenchmarkRunner.Run<SubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
BenchmarkRunner.Run<UnsubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
BenchmarkRunner.Run<AsyncLockBenchmark>();
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine("Press any key to exit");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<Type> CollectBenchmarks()
|
||||||
|
{
|
||||||
|
var benchmarks = new List<Type>();
|
||||||
|
|
||||||
|
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
if (type != typeof(BaseBenchmark))
|
||||||
|
{
|
||||||
|
benchmarks.Add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleArguments(string[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for preselection to avoid developer frustration.
|
||||||
|
|
||||||
|
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = benchmarkIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex < 0)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderMenu()
|
||||||
|
{
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class ReaderExtensionsBenchmark
|
||||||
|
{
|
||||||
|
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||||
|
MemoryStream stream;
|
||||||
|
|
||||||
|
[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 = MqttPacketFactories.Publish.Create(mqttMessage);
|
||||||
|
|
||||||
|
var buffer = mqttPacketFormatter.Encode(packet);
|
||||||
|
stream = new MemoryStream();
|
||||||
|
stream.Write(buffer.Packet);
|
||||||
|
stream.Write(buffer.Payload);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||||
|
|
||||||
|
packet = null;
|
||||||
|
consumed = input.Start;
|
||||||
|
observed = input.End;
|
||||||
|
bytesRead = 0;
|
||||||
|
var copy = input;
|
||||||
|
|
||||||
|
if (copy.Length < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy.Length < bodyLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
|
var buffer = GetMemory(bodySlice).ToArray();
|
||||||
|
|
||||||
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||||
|
|
||||||
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
|
{
|
||||||
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = formatter.Decode(receivedMqttPacket);
|
||||||
|
consumed = bodySlice.End;
|
||||||
|
observed = bodySlice.End;
|
||||||
|
bytesRead = headerLength + bodyLength;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||||
|
{
|
||||||
|
if (input.IsSingleSegment)
|
||||||
|
{
|
||||||
|
return input.First;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be rare
|
||||||
|
return input.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||||
|
{
|
||||||
|
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||||
|
var multiplier = 1;
|
||||||
|
var value = 0;
|
||||||
|
byte encodedByte;
|
||||||
|
var index = 1;
|
||||||
|
headerLength = 0;
|
||||||
|
bodyLength = 0;
|
||||||
|
|
||||||
|
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (index == temp.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedByte = temp[index];
|
||||||
|
index++;
|
||||||
|
|
||||||
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
|
if (multiplier > 128 * 128 * 128)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplier *= 128;
|
||||||
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
|
input = input.Slice(index);
|
||||||
|
|
||||||
|
headerLength = index;
|
||||||
|
bodyLength = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class RoundtripProcessingBenchmark
|
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ServerProcessingBenchmark
|
public class ServerProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -8,7 +12,7 @@ using System.Linq;
|
|||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class SubscribeBenchmark
|
public class SubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TcpPipesBenchmark
|
public class TcpPipesBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IDuplexPipe _client;
|
IDuplexPipe _client;
|
||||||
IDuplexPipe _server;
|
IDuplexPipe _server;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TopicFilterComparerBenchmark
|
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
static readonly char[] TopicLevelSeparator = { '/' };
|
static readonly char[] TopicLevelSeparator = { '/' };
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
public static void Generate(
|
public static void Generate(
|
||||||
int numPublishers, int numTopicsPerPublisher,
|
int numPublishers,
|
||||||
out Dictionary<string, List<string>> topicsByPublisher,
|
int numTopicsPerPublisher,
|
||||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
out Dictionary<string, List<string>> topicsByPublisher,
|
||||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||||
)
|
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||||
{
|
{
|
||||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
// Find some reasonable distribution across three topic levels
|
// Find some reasonable distribution across three topic levels
|
||||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
|
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||||
if (topicsPerLevel <= 0)
|
if (topicsPerLevel <= 0)
|
||||||
{
|
{
|
||||||
topicsPerLevel = 1;
|
topicsPerLevel = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int numLevel1Topics = topicsPerLevel;
|
var numLevel1Topics = topicsPerLevel;
|
||||||
int numLevel2Topics = topicsPerLevel;
|
var numLevel2Topics = topicsPerLevel;
|
||||||
|
|
||||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||||
if (maxNumLevel3Topics <= 0)
|
if (maxNumLevel3Topics <= 0)
|
||||||
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
for (var p = 0; p < numPublishers; ++p)
|
for (var p = 0; p < numPublishers; ++p)
|
||||||
{
|
{
|
||||||
int publisherTopicCount = 0;
|
var publisherTopicCount = 0;
|
||||||
var publisherName = "pub" + p;
|
var publisherName = "pub" + p;
|
||||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||||
{
|
{
|
||||||
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
|
|||||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||||
{
|
{
|
||||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
|
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||||
|
|
||||||
if (l2 == 0)
|
if (l2 == 0)
|
||||||
{
|
{
|
||||||
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
|
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
if ((l1 == 0) && (l3 == 0))
|
|
||||||
|
if (l1 == 0 && l3 == 0)
|
||||||
{
|
{
|
||||||
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
|
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
|
|||||||
topicList = new List<string>();
|
topicList = new List<string>();
|
||||||
topicsByPublisher.Add(publisherName, topicList);
|
topicsByPublisher.Add(publisherName, topicList);
|
||||||
}
|
}
|
||||||
|
|
||||||
topicList.Add(topic);
|
topicList.Add(topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -7,7 +11,7 @@ using System.Linq;
|
|||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class UnsubscribeBenchmark
|
public class UnsubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
|
|
||||||
<package id="System.Console" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
|
|
||||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
</packages>
|
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
||||||
{
|
{
|
||||||
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
|
return factory.CreateMqttRpcClient(
|
||||||
{
|
mqttClient,
|
||||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
new MqttRpcClientOptions
|
||||||
});
|
{
|
||||||
|
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||||
|
|||||||
@@ -2,23 +2,24 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
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 ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
|
||||||
readonly IMqttClient _mqttClient;
|
readonly IMqttClient _mqttClient;
|
||||||
readonly MqttRpcClientOptions _options;
|
readonly MqttRpcClientOptions _options;
|
||||||
|
|
||||||
|
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||||
|
|
||||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||||
{
|
{
|
||||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
@@ -27,6 +28,18 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||||
|
|
||||||
|
foreach (var tcs in _waitingCalls)
|
||||||
|
{
|
||||||
|
tcs.Value.TrySetCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
_waitingCalls.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||||
@@ -49,15 +62,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
if (methodName == null)
|
||||||
|
|
||||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
|
|
||||||
{
|
{
|
||||||
MethodName = methodName,
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
}
|
||||||
MqttClient = _mqttClient,
|
|
||||||
Options = _options
|
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||||
});
|
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||||
|
|
||||||
var requestTopic = topicNames.RequestTopic;
|
var requestTopic = topicNames.RequestTopic;
|
||||||
var responseTopic = topicNames.ResponseTopic;
|
var responseTopic = topicNames.ResponseTopic;
|
||||||
@@ -72,8 +83,7 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestMessage = new MqttApplicationMessageBuilder()
|
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
|
||||||
.WithTopic(requestTopic)
|
|
||||||
.WithPayload(payload)
|
.WithPayload(payload)
|
||||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||||
.WithResponseTopic(responseTopic)
|
.WithResponseTopic(responseTopic)
|
||||||
@@ -81,25 +91,23 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET452
|
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||||
var awaitable = new TaskCompletionSource<byte[]>();
|
|
||||||
#else
|
|
||||||
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||||
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
|
using (cancellationToken.Register(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
awaitable.TrySetCanceled();
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
return await awaitable.Task.ConfigureAwait(false);
|
return await awaitable.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -107,8 +115,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_waitingCalls.TryRemove(responseTopic, out _);
|
_waitingCalls.TryRemove(responseTopic, out _);
|
||||||
|
|
||||||
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
|
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,28 +127,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NET452
|
|
||||||
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
|
|
||||||
#else
|
|
||||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set this message to handled to that other code can avoid execution etc.
|
// Set this message to handled to that other code can avoid execution etc.
|
||||||
eventArgs.IsHandled = true;
|
eventArgs.IsHandled = true;
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
|
||||||
|
|
||||||
foreach (var tcs in _waitingCalls)
|
|
||||||
{
|
|
||||||
tcs.Value.TrySetCanceled();
|
|
||||||
}
|
|
||||||
|
|
||||||
_waitingCalls.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||||
|
|
||||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
|
||||||
{
|
|
||||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttRpcClientOptions Build()
|
public MqttRpcClientOptions Build()
|
||||||
{
|
{
|
||||||
return new MqttRpcClientOptions
|
return new MqttRpcClientOptions
|
||||||
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
TopicGenerationStrategy = _topicGenerationStrategy
|
TopicGenerationStrategy = _topicGenerationStrategy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||||
|
{
|
||||||
|
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
{
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public sealed class TopicGenerationContext
|
public sealed class TopicGenerationContext
|
||||||
{
|
{
|
||||||
public string MethodName { get; set; }
|
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
|
{
|
||||||
|
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||||
|
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||||
|
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
|
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MethodName { get; }
|
||||||
|
|
||||||
|
public IMqttClient MqttClient { get; }
|
||||||
|
|
||||||
|
public MqttRpcClientOptions Options { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the quality of service level.
|
/// 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.
|
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||||
/// There are 3 QoS levels in MQTT:
|
/// that defines the guarantee of delivery for a specific message.
|
||||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
/// There are 3 QoS levels in MQTT:
|
||||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
/// - 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>
|
/// </summary>
|
||||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||||
|
|
||||||
public IMqttClient MqttClient { get; set; }
|
|
||||||
|
|
||||||
public MqttRpcClientOptions Options { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
||||||
{
|
{
|
||||||
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
|
if (mqttFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
|
}
|
||||||
|
|
||||||
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using SuperSocket.ClientEngine;
|
using SuperSocket.ClientEngine;
|
||||||
using WebSocket4Net;
|
using WebSocket4Net;
|
||||||
|
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.WebSocket4Net
|
namespace MQTTnet.Extensions.WebSocket4Net
|
||||||
{
|
{
|
||||||
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
|
||||||
|
|
||||||
readonly MqttClientOptions _clientOptions;
|
readonly MqttClientOptions _clientOptions;
|
||||||
|
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||||
readonly MqttClientWebSocketOptions _webSocketOptions;
|
readonly MqttClientWebSocketOptions _webSocketOptions;
|
||||||
|
|
||||||
WebSocket _webSocket;
|
WebSocket _webSocket;
|
||||||
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate { get; }
|
||||||
|
|
||||||
public string Endpoint => _webSocketOptions.Uri;
|
public string Endpoint => _webSocketOptions.Uri;
|
||||||
|
|
||||||
public bool IsSecureConnection { get; private set; }
|
public bool IsSecureConnection { get; private set; }
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate { get; }
|
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var uri = _webSocketOptions.Uri;
|
var uri = _webSocketOptions.Uri;
|
||||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
uri = "wss://" + uri;
|
uri = "wss://" + uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
#if NET48 || NETCOREAPP3_0_OR_GREATER
|
||||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||||
#else
|
#else
|
||||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
||||||
@@ -90,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
var receiveBufferSize = 0;
|
var receiveBufferSize = 0;
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
var certificates = new X509CertificateCollection();
|
||||||
if (_webSocketOptions?.TlsOptions?.Certificates != null)
|
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
||||||
{
|
{
|
||||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
||||||
{
|
{
|
||||||
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
NoDelay = true,
|
NoDelay = true,
|
||||||
Security =
|
Security =
|
||||||
{
|
{
|
||||||
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
|
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
|
||||||
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
|
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||||
Certificates = certificates
|
Certificates = certificates
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
|
return ConnectInternalAsync(cancellationToken);
|
||||||
|
|
||||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||||
@@ -124,10 +125,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
_webSocket.Close();
|
_webSocket.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_webSocket == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocket.DataReceived -= OnDataReceived;
|
||||||
|
_webSocket.Error -= OnError;
|
||||||
|
_webSocket.Dispose();
|
||||||
|
_webSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var readBytes = 0;
|
var readBytes = 0;
|
||||||
@@ -161,75 +175,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_webSocket == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_webSocket.DataReceived -= OnDataReceived;
|
|
||||||
_webSocket.Error -= OnError;
|
|
||||||
_webSocket.Dispose();
|
|
||||||
_webSocket = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnError(object sender, ErrorEventArgs e)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.Write(e.Exception.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
|
||||||
{
|
|
||||||
foreach (var @byte in e.Data)
|
|
||||||
{
|
|
||||||
_receiveBuffer.Add(@byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_webSocket.Error += OnError;
|
_webSocket.Error += OnError;
|
||||||
_webSocket.DataReceived += OnDataReceived;
|
_webSocket.DataReceived += OnDataReceived;
|
||||||
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<Exception>();
|
var promise = new AsyncTaskCompletionSource<bool>();
|
||||||
|
|
||||||
void ErrorHandler(object sender, ErrorEventArgs e)
|
void ErrorHandler(object sender, ErrorEventArgs e)
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult(e.Exception);
|
promise.TrySetException(e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuccessHandler(object sender, EventArgs e)
|
void OpenedHandler(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult(null);
|
promise.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClosedHandler(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
promise.TrySetException(new MqttCommunicationException("Connection closed."));
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_webSocket.Opened += SuccessHandler;
|
_webSocket.Opened += OpenedHandler;
|
||||||
|
_webSocket.Closed += ClosedHandler;
|
||||||
_webSocket.Error += ErrorHandler;
|
_webSocket.Error += ErrorHandler;
|
||||||
|
|
||||||
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
|
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
|
||||||
_webSocket.Open();
|
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
||||||
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
|
|
||||||
|
|
||||||
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
|
|
||||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
|
||||||
{
|
{
|
||||||
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
|
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
// OpenAsync does basically the same but without support for a cancellation token.
|
||||||
|
// So we do it on our own!
|
||||||
|
_webSocket.Open();
|
||||||
|
|
||||||
|
await promise.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||||
if (timeoutReached)
|
if (timeoutReached)
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationTimedOutException(exception);
|
throw new MqttCommunicationTimedOutException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is AuthenticationException authenticationException)
|
if (exception is AuthenticationException authenticationException)
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationException(authenticationException.InnerException);
|
throw new MqttCommunicationException(authenticationException.InnerException);
|
||||||
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_webSocket.Opened -= SuccessHandler;
|
_webSocket.Opened -= OpenedHandler;
|
||||||
|
_webSocket.Closed -= ClosedHandler;
|
||||||
_webSocket.Error -= ErrorHandler;
|
_webSocket.Error -= ErrorHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var @byte in e.Data)
|
||||||
|
{
|
||||||
|
_receiveBuffer.Add(@byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(object sender, ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.Write(e.Exception.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.WebSocket4Net
|
namespace MQTTnet.Extensions.WebSocket4Net
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
switch (options.ChannelOptions)
|
switch (options.ChannelOptions)
|
||||||
{
|
{
|
||||||
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new MqttTcpChannel(options),
|
new MqttTcpChannel(options),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
packetInspector,
|
||||||
logger);
|
logger);
|
||||||
}
|
}
|
||||||
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
packetInspector,
|
||||||
logger);
|
logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,11 @@
|
|||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -17,10 +18,6 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
public static async Task RunAsync()
|
public static async Task RunAsync()
|
||||||
{
|
{
|
||||||
// MqttNetConsoleLogger.ForwardToConsole();
|
|
||||||
|
|
||||||
// For most of these connections to work, set output target to Net5.0.
|
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
// TLS13 is only available in Net5.0
|
// TLS13 is only available in Net5.0
|
||||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
||||||
@@ -31,7 +28,6 @@ namespace MQTTnet.TestApp
|
|||||||
CertificateValidationHandler = w => true
|
CertificateValidationHandler = w => true
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
||||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
||||||
{
|
{
|
||||||
@@ -42,33 +38,37 @@ namespace MQTTnet.TestApp
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mqtt.eclipseprojects.io
|
// mqtt.eclipseprojects.io
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
|
"mqtt.eclipseprojects.io TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
|
"mqtt.eclipseprojects.io WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
||||||
|
true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// test.mosquitto.org
|
// test.mosquitto.org
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
|
"test.mosquitto.org TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
|
"test.mosquitto.org TCP - Authenticated",
|
||||||
.WithCredentials("rw", "readwrite")
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
"test.mosquitto.org TCP TLS12",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
||||||
@@ -76,27 +76,40 @@ namespace MQTTnet.TestApp
|
|||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
|
await ExecuteTestAsync(
|
||||||
|
"test.mosquitto.org TCP TLS12 - Authenticated",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||||
.WithCredentials("rw", "readwrite")
|
.WithCredentials("rw", "readwrite")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||||
|
.WithTls(unsafeTls12)
|
||||||
|
.Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
|
"test.mosquitto.org WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org WS TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
|
"test.mosquitto.org WS (WebSocket4Net)",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"test.mosquitto.org WS TLS12",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
|
// await ExecuteTestAsync(
|
||||||
|
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||||
|
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||||
|
// true);
|
||||||
|
|
||||||
// broker.emqx.io
|
// broker.emqx.io
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
|
"broker.emqx.io TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
"broker.emqx.io TCP TLS12",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
||||||
@@ -104,23 +117,37 @@ namespace MQTTnet.TestApp
|
|||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
|
"broker.emqx.io WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io WS TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
|
"broker.emqx.io WS (WebSocket4Net)",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.emqx.io WS TLS12",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
// broker.hivemq.com
|
// broker.hivemq.com
|
||||||
await ExecuteTestAsync("broker.hivemq.com TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
|
"broker.hivemq.com TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.hivemq.com WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
|
"broker.hivemq.com WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.hivemq.com WS (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
// mqtt.swifitch.cz: Does not seem to operate any more
|
// mqtt.swifitch.cz: Does not seem to operate any more
|
||||||
|
|
||||||
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task ExecuteTestAsync(string name, MqttClientOptions options)
|
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
var factory = new MqttFactory();
|
||||||
//factory.UseWebSocket4Net();
|
if (useWebSocket4Net)
|
||||||
var client = factory.CreateMqttClient();
|
|
||||||
var topic = Guid.NewGuid().ToString();
|
|
||||||
|
|
||||||
MqttApplicationMessage receivedMessage = null;
|
|
||||||
client.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
{
|
||||||
receivedMessage = e.ApplicationMessage;
|
factory.UseWebSocket4Net();
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await client.ConnectAsync(options);
|
|
||||||
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
|
||||||
|
|
||||||
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
|
||||||
{
|
|
||||||
throw new Exception("Message invalid.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.UnsubscribeAsync(topic);
|
using (var client = factory.CreateMqttClient())
|
||||||
await client.DisconnectAsync();
|
{
|
||||||
|
MqttApplicationMessage receivedMessage = null;
|
||||||
|
client.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
receivedMessage = e.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var topic = Guid.NewGuid().ToString();
|
||||||
|
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||||
|
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||||
|
|
||||||
|
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
||||||
|
|
||||||
|
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
||||||
|
{
|
||||||
|
throw new Exception("Message invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.UnsubscribeAsync(topic).ConfigureAwait(false);
|
||||||
|
await client.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
Write("[OK]\n", ConsoleColor.Green);
|
Write("[OK]\n", ConsoleColor.Green);
|
||||||
}
|
}
|
||||||
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
|
|||||||
Console.ForegroundColor = color;
|
Console.ForegroundColor = color;
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,10 +47,12 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
await managedClient.StartAsync(managedClientOptions);
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
// Send test data.
|
// Send test data.
|
||||||
await senderClient.PublishStringAsync("topic1");
|
await senderClient.PublishStringAsync("topic1");
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
receivedMessages.AssertReceivedCountEquals(1);
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
@@ -329,13 +331,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||||
await sendingClient.PublishAsync(
|
await sendingClient.PublishStringAsync("topic", "A", retain: true);
|
||||||
new MqttApplicationMessage
|
|
||||||
{
|
|
||||||
Topic = "topic",
|
|
||||||
Payload = new byte[] { 1 },
|
|
||||||
Retain = true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait a bit for the retained message to be available
|
// Wait a bit for the retained message to be available
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
@@ -357,18 +353,15 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
await managedClient.SubscribeAsync("topic");
|
await managedClient.SubscribeAsync("topic");
|
||||||
|
|
||||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedManagedMessages.Count);
|
Assert.AreEqual(1, receivedManagedMessages.Count);
|
||||||
|
|
||||||
await managedClient.StopAsync();
|
await managedClient.StopAsync();
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
// After reconnect and then some delay, the retained message must not be received,
|
// After reconnect and then some delay, the retained message must not be received,
|
||||||
@@ -377,7 +370,6 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
// Make sure that it gets received after subscribing again.
|
// Make sure that it gets received after subscribing again.
|
||||||
await managedClient.SubscribeAsync("topic");
|
await managedClient.SubscribeAsync("topic");
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||||
|
|||||||
@@ -6,68 +6,93 @@ using System;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Tests.Mockups;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests.Extensions
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class RPC_Tests : BaseTestClass
|
public sealed class Rpc_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
var responseSender = await testEnvironment.ConnectClient();
|
||||||
|
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||||
|
{
|
||||||
|
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||||
|
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||||
|
|
||||||
|
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0()
|
public Task Execute_Success_With_QoS_0()
|
||||||
{
|
{
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
|
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_1()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_2()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0_MQTT_V5()
|
public Task Execute_Success_With_QoS_0_MQTT_V5()
|
||||||
{
|
{
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
|
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
|
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_1()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
|
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_2()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
@@ -90,6 +115,31 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||||
|
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
var responseSender = await testEnvironment.ConnectClient();
|
||||||
|
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||||
|
{
|
||||||
|
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||||
|
await CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||||
|
|
||||||
|
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||||
public async Task Execute_With_Custom_Topic_Names()
|
public async Task Execute_With_Custom_Topic_Names()
|
||||||
@@ -98,14 +148,24 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
|
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
|
||||||
|
|
||||||
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_Factory()
|
||||||
|
{
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var rpcClient = factory.CreateMqttRpcClient(client);
|
||||||
|
|
||||||
|
Assert.IsNotNull(rpcClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
|
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
@@ -151,57 +211,6 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
var responseSender = await testEnvironment.ConnectClient();
|
|
||||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
|
||||||
{
|
|
||||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
|
||||||
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
|
||||||
|
|
||||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
|
||||||
{
|
|
||||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
var responseSender = await testEnvironment.ConnectClient();
|
|
||||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
|
||||||
{
|
|
||||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
|
||||||
await CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
|
||||||
|
|
||||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
|
||||||
{
|
|
||||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
|
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||||
{
|
{
|
||||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
@@ -214,4 +223,4 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Extensions
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class WebSocket4Net_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttCommunicationException))]
|
||||||
|
public async Task Connect_Failed_With_Invalid_Server()
|
||||||
|
{
|
||||||
|
var factory = new MqttFactory().UseWebSocket4Net();
|
||||||
|
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||||
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_Correct_Adapter()
|
||||||
|
{
|
||||||
|
// Should not throw exception
|
||||||
|
var factory = new MqttFactory().UseWebSocket4Net();
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var adapterFactory = client.GetFieldValue("_adapterFactory");
|
||||||
|
|
||||||
|
Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_WebSocket4Net()
|
||||||
|
{
|
||||||
|
// Should not throw exception
|
||||||
|
new MqttFactory().UseWebSocket4Net();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Formatter
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttBufferReader_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||||
|
public void Fire_Exception_If_Not_Enough_Data()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
reader.SetBuffer(buffer, 0, 3);
|
||||||
|
|
||||||
|
// 1 byte is missing.
|
||||||
|
reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||||
|
public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
reader.SetBuffer(buffer, 0, 3);
|
||||||
|
|
||||||
|
// 1 byte is missing.
|
||||||
|
reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Is_EndOfStream_Without_Buffer()
|
||||||
|
{
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Read_Remaining_Data_From_Larger_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
// This should only read 5 bytes even if more data is in the buffer
|
||||||
|
// due to custom bounds.
|
||||||
|
var remainingData = reader.ReadRemainingData();
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft);
|
||||||
|
Assert.AreEqual(5, remainingData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Read_Various_Positions_and_Offsets()
|
||||||
|
{
|
||||||
|
const int NumBufferElements = 1000;
|
||||||
|
const int NumTestSequences = 10000;
|
||||||
|
|
||||||
|
const string TestString = "The quick brown fox jumps over the lazy dog.";
|
||||||
|
|
||||||
|
var rndSeed = Environment.TickCount;
|
||||||
|
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
|
||||||
|
var rnd = new Random(rndSeed);
|
||||||
|
// rndSeed is printed when an assert fails
|
||||||
|
|
||||||
|
var bufferElements = new List<ElementReference>();
|
||||||
|
|
||||||
|
byte[] elementBuffer;
|
||||||
|
|
||||||
|
// Fill a buffer with strings or numbers in big endian format
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var bw = new BinaryWriter(ms))
|
||||||
|
{
|
||||||
|
var bufferOffset = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < NumBufferElements; i++)
|
||||||
|
{
|
||||||
|
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
|
||||||
|
|
||||||
|
byte[] elementBytes = null;
|
||||||
|
uint elementNumberValue = 0; // all element number values fit into uint
|
||||||
|
string elementStringValue = null;
|
||||||
|
var elementSize = 0;
|
||||||
|
|
||||||
|
var alreadyBigEndian = false;
|
||||||
|
switch (nextElementType)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
var byteValue = (byte)i;
|
||||||
|
elementNumberValue = byteValue;
|
||||||
|
elementSize = sizeof(byte);
|
||||||
|
elementBytes = new byte[1] { byteValue };
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
var ushortValue = (ushort)i;
|
||||||
|
elementNumberValue = ushortValue;
|
||||||
|
elementSize = sizeof(ushort);
|
||||||
|
elementBytes = BitConverter.GetBytes(ushortValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
var uintValue = (uint)i;
|
||||||
|
elementNumberValue = uintValue;
|
||||||
|
elementSize = sizeof(uint);
|
||||||
|
elementBytes = BitConverter.GetBytes(uintValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = (uint)i;
|
||||||
|
var writer = new MqttBufferWriter(4, 4);
|
||||||
|
writer.WriteVariableByteInteger(elementNumberValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
var stringLen = rnd.Next(TestString.Length);
|
||||||
|
elementStringValue = TestString.Substring(0, stringLen); // could be empty
|
||||||
|
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
|
||||||
|
writer.WriteString(elementStringValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(elementBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.Write(elementBytes);
|
||||||
|
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
|
||||||
|
bufferOffset += elementSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
elementBuffer = ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < NumTestSequences; i++)
|
||||||
|
{
|
||||||
|
var firstElementIndex = rnd.Next(NumBufferElements - 1);
|
||||||
|
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
|
||||||
|
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
|
||||||
|
var lastElementIndex = firstElementIndex + elementCount - 1;
|
||||||
|
// Use element index to get buffer offsets
|
||||||
|
var lastElement = bufferElements[lastElementIndex];
|
||||||
|
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
|
||||||
|
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
|
||||||
|
var segmentLength = segmentEndPosition - segmentStartPosition;
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
|
||||||
|
|
||||||
|
// read all elements in the buffer segment; values should be as expected
|
||||||
|
for (var n = 0; n < elementCount; n++)
|
||||||
|
{
|
||||||
|
var element = bufferElements[firstElementIndex + n];
|
||||||
|
uint elementNumberValue = 0;
|
||||||
|
string elementStringValue = null;
|
||||||
|
|
||||||
|
var expectedPosition = element.BufferOffset - segmentStartPosition;
|
||||||
|
Assert.AreEqual(expectedPosition, reader.Position);
|
||||||
|
|
||||||
|
switch (element.Type)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadByte();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadTwoByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadVariableByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
elementStringValue = reader.ReadString();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementStringValue != null)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm end of stream
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
|
||||||
|
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Full_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Partial_End_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 5, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Partial_Start_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper class to build up a reference to elements of various types in a buffer
|
||||||
|
class ElementReference
|
||||||
|
{
|
||||||
|
public enum BufferElementType
|
||||||
|
{
|
||||||
|
Byte,
|
||||||
|
TwoByteInt,
|
||||||
|
FourByteInt,
|
||||||
|
VariableSizeInt,
|
||||||
|
String
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int NumBufferElementTypes = 5;
|
||||||
|
|
||||||
|
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Size = size;
|
||||||
|
NumberValue = numberValue;
|
||||||
|
StringValue = stringValue;
|
||||||
|
BufferOffset = bufferOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BufferOffset { get; }
|
||||||
|
public uint NumberValue { get; }
|
||||||
|
public int Size { get; }
|
||||||
|
public string StringValue { get; }
|
||||||
|
public BufferElementType Type { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Tests.Extensions;
|
using MQTTnet.Tests.Helpers;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Formatter
|
namespace MQTTnet.Tests.Formatter
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Helpers
|
||||||
|
{
|
||||||
|
public static class MqttClientExtensions
|
||||||
|
{
|
||||||
|
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
|
||||||
|
{
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestApplicationMessageReceivedHandler(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Extensions
|
namespace MQTTnet.Tests.Helpers
|
||||||
{
|
{
|
||||||
public static class MqttPacketWriterExtensions
|
public static class MqttPacketWriterExtensions
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Helpers
|
||||||
|
{
|
||||||
|
public static class ReflectionExtensions
|
||||||
|
{
|
||||||
|
public static object GetFieldValue(this object source, string fieldName)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Field {fieldName} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.GetValue(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,21 +7,20 @@
|
|||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||||
|
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
Assert.AreEqual(1, receivedMessagesCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,17 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
|
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_receivedEventArgs)
|
||||||
|
{
|
||||||
|
return _receivedEventArgs.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
|
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
|
|||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class Protocol_Tests
|
public sealed class Protocol_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Encode_Four_Byte_Integer()
|
public void Encode_Four_Byte_Integer()
|
||||||
{
|
{
|
||||||
var writer = new MqttBufferWriter(4, 4);
|
var writer = new MqttBufferWriter(4, 4);
|
||||||
|
|
||||||
for (uint value = 0; value < 268435455; value++)
|
for (uint value = 0; value < 268435455; value++)
|
||||||
{
|
{
|
||||||
writer.WriteVariableByteInteger(value);
|
writer.WriteVariableByteInteger(value);
|
||||||
|
|
||||||
var buffer = writer.GetBuffer();
|
var buffer = writer.GetBuffer();
|
||||||
|
|
||||||
var reader = new MqttBufferReader();
|
var reader = new MqttBufferReader();
|
||||||
reader.SetBuffer(buffer, 0, writer.Length);
|
reader.SetBuffer(buffer, 0, writer.Length);
|
||||||
var checkValue = reader.ReadVariableByteInteger();
|
var checkValue = reader.ReadVariableByteInteger();
|
||||||
|
|
||||||
Assert.AreEqual(value, checkValue);
|
Assert.AreEqual(value, checkValue);
|
||||||
|
|
||||||
|
writer.Reset(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Encode_Two_Byte_Integer()
|
||||||
|
{
|
||||||
|
var writer = new MqttBufferWriter(2, 2);
|
||||||
|
|
||||||
|
for (ushort value = 0; value < ushort.MaxValue; value++)
|
||||||
|
{
|
||||||
|
writer.WriteTwoByteInteger(value);
|
||||||
|
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(buffer, 0, writer.Length);
|
||||||
|
var checkValue = reader.ReadTwoByteInteger();
|
||||||
|
|
||||||
|
Assert.AreEqual(value, checkValue);
|
||||||
|
|
||||||
writer.Reset(0);
|
writer.Reset(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Injected_Application_Message()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
MqttApplicationMessage interceptedMessage = null;
|
||||||
|
server.InterceptingPublishAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
interceptedMessage = eventArgs.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||||
|
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.IsNotNull(interceptedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,6 +114,104 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard1()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/+/room");
|
||||||
|
await SubscribeToTopic("house/+/room/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
CheckIsSubscribed("house/2/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||||
|
CheckIsNotSubscribed("house/1/study/bed");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard2()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/+/room");
|
||||||
|
await SubscribeToTopic("house/+/room/#");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room");
|
||||||
|
CheckIsSubscribed("house/2/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room/bed/cover");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/level");
|
||||||
|
CheckIsNotSubscribed("house/1/level/door");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard3()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/room");
|
||||||
|
await SubscribeToTopic("house/1/room/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/2/room");
|
||||||
|
CheckIsNotSubscribed("house/2/room/bed/cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard4()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/+/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/room");
|
||||||
|
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard5()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/+/#");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
CheckIsSubscribed("house/1/room/chair/leg");
|
||||||
|
CheckIsSubscribed("house/1/level/window");
|
||||||
|
CheckIsSubscribed("house/1/level/door");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/2/room/bed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task SubscribeToTopic(string topic)
|
||||||
|
{
|
||||||
|
var sp = new MqttSubscribePacket
|
||||||
|
{
|
||||||
|
TopicFilters = new List<MqttTopicFilter>
|
||||||
|
{
|
||||||
|
new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIsSubscribed(string topic)
|
||||||
|
{
|
||||||
|
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||||
|
Assert.IsTrue(result.IsSubscribed);
|
||||||
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIsNotSubscribed(string topic)
|
||||||
|
{
|
||||||
|
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||||
|
Assert.IsFalse(result.IsSubscribed);
|
||||||
|
}
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
|
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
|
||||||
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
|
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
dummyClient.Dispose();
|
dummyClient.Dispose();
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
// Now inject messages which are appended to the queue of the client.
|
// Now inject messages which are appended to the queue of the client.
|
||||||
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|||||||
@@ -121,10 +121,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NET452
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
[DataRow(MqttProtocolVersion.V310)]
|
||||||
[DataRow(MqttProtocolVersion.V311)]
|
[DataRow(MqttProtocolVersion.V311)]
|
||||||
[DataRow(MqttProtocolVersion.V500)]
|
[DataRow(MqttProtocolVersion.V500)]
|
||||||
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
|
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
|
||||||
@@ -150,8 +149,6 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Try to connect 50 clients at the same time.
|
// Try to connect 50 clients at the same time.
|
||||||
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
|
||||||
|
|
||||||
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
@@ -162,13 +159,12 @@ namespace MQTTnet.Tests.Server
|
|||||||
var sendClient = await testEnvironment.ConnectClient(option2);
|
var sendClient = await testEnvironment.ConnectClient(option2);
|
||||||
await sendClient.PublishStringAsync("aaa", "1");
|
await sendClient.PublishStringAsync("aaa", "1");
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(true, hasReceive);
|
Assert.AreEqual(true, hasReceive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||||
|
|||||||
@@ -13,12 +13,56 @@ using MQTTnet.Internal;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Subscribe_Tests : BaseTestClass
|
public sealed class Subscribe_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("A", "A", true)]
|
||||||
|
[DataRow("A", "B", false)]
|
||||||
|
[DataRow("A", "#", true)]
|
||||||
|
[DataRow("A", "+", true)]
|
||||||
|
[DataRow("A/B", "A/B", true)]
|
||||||
|
[DataRow("A/B", "A/+", true)]
|
||||||
|
[DataRow("A/B", "A/#", true)]
|
||||||
|
[DataRow("A/B/C", "A/B/C", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/C", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/+", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/#", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/B/C/D", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/+/C/+", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/+/C/#", true)]
|
||||||
|
[DataRow("A/B/C", "A/B/+", true)]
|
||||||
|
[DataRow("A/B1/B2/C", "A/+/C", false)]
|
||||||
|
public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork)
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
await receiver.SubscribeAsync(filter).ConfigureAwait(false);
|
||||||
|
var receivedMessages = receiver.TrackReceivedMessages();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false);
|
||||||
|
|
||||||
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (shouldWork)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Deny_Invalid_Topic()
|
public async Task Deny_Invalid_Topic()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -10,33 +9,51 @@ namespace MQTTnet.Tests.Server
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Will_Tests : BaseTestClass
|
public sealed class Will_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Will_Message()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
|
MqttApplicationMessage willMessage = null;
|
||||||
|
server.InterceptingPublishAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
willMessage = eventArgs.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
|
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.IsNotNull(willMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||||
|
var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
|
await sender.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count);
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,24 +64,17 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
var receivedMessagesCount = 0;
|
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClient : Disposable, IMqttClient
|
public sealed class MqttClient : Disposable, IMqttClient
|
||||||
{
|
{
|
||||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
|
||||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||||
|
|
||||||
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
||||||
@@ -32,7 +31,6 @@ namespace MQTTnet.Client
|
|||||||
readonly object _disconnectLock = new object();
|
readonly object _disconnectLock = new object();
|
||||||
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
readonly AsyncEvent<InspectMqttPacketEventArgs> _inspectPacketEvent = new AsyncEvent<InspectMqttPacketEventArgs>();
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
|
||||||
|
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
@@ -207,7 +205,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (clientWasConnected)
|
if (clientWasConnected)
|
||||||
{
|
{
|
||||||
var disconnectPacket = _packetFactories.Disconnect.Create(options);
|
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +239,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var publishPacket = _packetFactories.Publish.Create(applicationMessage);
|
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||||
|
|
||||||
switch (applicationMessage.QualityOfServiceLevel)
|
switch (applicationMessage.QualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
@@ -301,7 +299,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var subscribePacket = _packetFactories.Subscribe.Create(options);
|
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -319,7 +317,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
|
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -347,7 +345,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
|
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||||
return SendAsync(pubAckPacket, cancellationToken);
|
return SendAsync(pubAckPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,7 +353,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
|
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||||
return SendAsync(pubRecPacket, cancellationToken);
|
return SendAsync(pubRecPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,7 +371,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connectPacket = _packetFactories.Connect.Create(options);
|
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||||
|
|
||||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -535,7 +533,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||||
|
|
||||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
@@ -601,7 +599,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
// The packet is unknown. Probably due to a restart of the client.
|
// The packet is unknown. Probably due to a restart of the client.
|
||||||
// So wen send this to the server to trigger a full resend of the message.
|
// So wen send this to the server to trigger a full resend of the message.
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||||
return SendAsync(pubRelPacket, cancellationToken);
|
return SendAsync(pubRelPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,7 +608,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
return SendAsync(pubCompPacket, cancellationToken);
|
return SendAsync(pubCompPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,7 +635,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
|
||||||
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,100 @@ using System;
|
|||||||
|
|
||||||
namespace MQTTnet.Diagnostics
|
namespace MQTTnet.Diagnostics
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
|
||||||
|
*/
|
||||||
|
|
||||||
public static class MqttNetSourceLoggerExtensions
|
public static class MqttNetSourceLoggerExtensions
|
||||||
{
|
{
|
||||||
/*
|
public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
||||||
* The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
|
|
||||||
{
|
{
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new MqttNetSourceLogger(logger, source);
|
logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, null, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(this MqttNetSourceLogger logger, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1 }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1, parameter2 }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info(this MqttNetSourceLogger logger, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Publish<TParameter1>(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(logLevel, message, new object[] { parameter1 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Publish<TParameter1, TParameter2>(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
@@ -26,7 +109,7 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Verbose<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
@@ -36,10 +119,14 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1, TParameter2, TParameter3>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2,
|
public static void Verbose<TParameter1, TParameter2, TParameter3>(
|
||||||
|
this MqttNetSourceLogger logger,
|
||||||
|
string message,
|
||||||
|
TParameter1 parameter1,
|
||||||
|
TParameter2 parameter2,
|
||||||
TParameter3 parameter3)
|
TParameter3 parameter3)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
@@ -47,7 +134,7 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2, parameter3}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2, parameter3 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose(this MqttNetSourceLogger logger, string message)
|
public static void Verbose(this MqttNetSourceLogger logger, string message)
|
||||||
@@ -60,134 +147,74 @@ namespace MQTTnet.Diagnostics
|
|||||||
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1, parameter2}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info(this MqttNetSourceLogger logger, string message)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message)
|
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning(this MqttNetSourceLogger logger, string message)
|
public static void Warning(this MqttNetSourceLogger logger, string message)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (logger == null)
|
||||||
{
|
{
|
||||||
return;
|
throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1}, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1, parameter2}, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
|
return new MqttNetSourceLogger(logger, source);
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, null, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(this MqttNetSourceLogger logger, string message)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, null, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,9 @@ using MQTTnet.Packets;
|
|||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttApplicationMessageFactory
|
public static class MqttApplicationMessageFactory
|
||||||
{
|
{
|
||||||
public MqttApplicationMessage Create(MqttPublishPacket publishPacket)
|
public static MqttApplicationMessage Create(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,21 +7,24 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttBufferReader
|
public sealed class MqttBufferReader
|
||||||
{
|
{
|
||||||
byte[] _buffer = EmptyBuffer.Array;
|
byte[] _buffer = EmptyBuffer.Array;
|
||||||
int _initialPosition;
|
int _maxPosition;
|
||||||
int _length;
|
int _offset;
|
||||||
int _position;
|
int _position;
|
||||||
|
|
||||||
public bool EndOfStream => _position == _length;
|
public int BytesLeft => _maxPosition - _position;
|
||||||
|
|
||||||
public int Position => _position;
|
public bool EndOfStream => BytesLeft == 0;
|
||||||
|
|
||||||
public int BytesLeft => _length - _position;
|
public int Position => _position - _offset;
|
||||||
|
|
||||||
public byte[] ReadBinaryData()
|
public byte[] ReadBinaryData()
|
||||||
{
|
{
|
||||||
@@ -35,7 +38,7 @@ namespace MQTTnet.Formatter
|
|||||||
ValidateReceiveBuffer(length);
|
ValidateReceiveBuffer(length);
|
||||||
|
|
||||||
var result = new byte[length];
|
var result = new byte[length];
|
||||||
Array.Copy(_buffer, _position, result, 0, length);
|
MqttMemoryHelper.Copy(_buffer, _position, result, 0, length);
|
||||||
_position += length;
|
_position += length;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -44,7 +47,6 @@ namespace MQTTnet.Formatter
|
|||||||
public byte ReadByte()
|
public byte ReadByte()
|
||||||
{
|
{
|
||||||
ValidateReceiveBuffer(1);
|
ValidateReceiveBuffer(1);
|
||||||
|
|
||||||
return _buffer[_position++];
|
return _buffer[_position++];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,25 +54,32 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
ValidateReceiveBuffer(4);
|
ValidateReceiveBuffer(4);
|
||||||
|
|
||||||
var byte0 = _buffer[_position++];
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
var byte1 = _buffer[_position++];
|
var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position));
|
||||||
var byte2 = _buffer[_position++];
|
#else
|
||||||
var byte3 = _buffer[_position++];
|
var byte0 = _buffer[_position];
|
||||||
|
var byte1 = _buffer[_position + 1];
|
||||||
|
var byte2 = _buffer[_position + 2];
|
||||||
|
var byte3 = _buffer[_position + 3];
|
||||||
|
|
||||||
return (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
|
var value = (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_position += 4;
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] ReadRemainingData()
|
public byte[] ReadRemainingData()
|
||||||
{
|
{
|
||||||
var bufferLength = _length - _position;
|
var bufferLength = BytesLeft;
|
||||||
|
|
||||||
if (bufferLength == 0)
|
if (bufferLength == 0)
|
||||||
{
|
{
|
||||||
return EmptyBuffer.Array;
|
return EmptyBuffer.Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[bufferLength];
|
var buffer = new byte[bufferLength];
|
||||||
Array.Copy(_buffer, _position, buffer, 0, bufferLength);
|
MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength);
|
||||||
|
_position += bufferLength;
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
@@ -83,10 +92,16 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidateReceiveBuffer(length);
|
ValidateReceiveBuffer(length);
|
||||||
|
|
||||||
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
|
// AsSpan() version is slightly faster. Not much but at least a little bit.
|
||||||
|
var result = Encoding.UTF8.GetString(_buffer.AsSpan(_position, length));
|
||||||
|
#else
|
||||||
var result = Encoding.UTF8.GetString(_buffer, _position, length);
|
var result = Encoding.UTF8.GetString(_buffer, _position, length);
|
||||||
|
#endif
|
||||||
|
|
||||||
_position += length;
|
_position += length;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -95,10 +110,17 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
ValidateReceiveBuffer(2);
|
ValidateReceiveBuffer(2);
|
||||||
|
|
||||||
var msb = _buffer[_position++];
|
#if NETCOREAPP3_0_OR_GREATER
|
||||||
var lsb = _buffer[_position++];
|
var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position));
|
||||||
|
#else
|
||||||
|
var msb = _buffer[_position];
|
||||||
|
var lsb = _buffer[_position + 1];
|
||||||
|
|
||||||
return (ushort)((msb << 8) | lsb);
|
var value = (ushort)((msb << 8) | lsb);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_position += 2;
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint ReadVariableByteInteger()
|
public uint ReadVariableByteInteger()
|
||||||
@@ -125,23 +147,29 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
public void Seek(int position)
|
public void Seek(int position)
|
||||||
{
|
{
|
||||||
_position = _initialPosition + position;
|
_position = _offset + position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetBuffer(byte[] buffer, int position, int length)
|
public void SetBuffer(ArraySegment<byte> buffer)
|
||||||
{
|
{
|
||||||
_buffer = buffer;
|
SetBuffer(buffer.Array, buffer.Offset, buffer.Count);
|
||||||
_initialPosition = position;
|
}
|
||||||
_position = position;
|
|
||||||
_length = length;
|
public void SetBuffer(byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
_buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
|
||||||
|
_offset = offset;
|
||||||
|
_position = offset;
|
||||||
|
_maxPosition = offset + length;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
void ValidateReceiveBuffer(int length)
|
void ValidateReceiveBuffer(int length)
|
||||||
{
|
{
|
||||||
if (_length < _position + length)
|
var newPosition = _position + length;
|
||||||
|
if (_maxPosition < newPosition)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Expected at least {Position + length} bytes but there are only {_length} bytes");
|
throw new MqttProtocolViolationException($"Expected at least {newPosition} bytes but there are only {_maxPosition} bytes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
@@ -63,17 +64,30 @@ namespace MQTTnet.Formatter
|
|||||||
return _buffer;
|
return _buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int GetLengthOfVariableInteger(uint value)
|
public static int GetVariableByteIntegerSize(uint value)
|
||||||
{
|
{
|
||||||
var result = 0;
|
// From RFC: Table 2.4 Size of Remaining Length field
|
||||||
var x = value;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
x /= 128;
|
|
||||||
result++;
|
|
||||||
} while (x > 0);
|
|
||||||
|
|
||||||
return result;
|
// 0 (0x00) to 127 (0x7F)
|
||||||
|
if (value <= 127)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 128 (0x80, 0x01) to 16 383 (0xFF, 0x7F)
|
||||||
|
if (value <= 16383)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16 384 (0x80, 0x80, 0x01) to 2 097 151 (0xFF, 0xFF, 0x7F)
|
||||||
|
if (value <= 2097151)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 097 152 (0x80, 0x80, 0x80, 0x01) to 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)
|
||||||
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(int length)
|
public void Reset(int length)
|
||||||
@@ -90,18 +104,12 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
public void Write(MqttBufferWriter propertyWriter)
|
public void Write(MqttBufferWriter propertyWriter)
|
||||||
{
|
{
|
||||||
if (propertyWriter is MqttBufferWriter writer)
|
|
||||||
{
|
|
||||||
WriteBinary(writer._buffer, 0, writer.Length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyWriter == null)
|
if (propertyWriter == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(propertyWriter));
|
throw new ArgumentNullException(nameof(propertyWriter));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidOperationException($"{nameof(propertyWriter)} must be of type {nameof(MqttBufferWriter)}");
|
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteBinary(byte[] buffer, int offset, int count)
|
public void WriteBinary(byte[] buffer, int offset, int count)
|
||||||
@@ -118,7 +126,7 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
EnsureAdditionalCapacity(count);
|
EnsureAdditionalCapacity(count);
|
||||||
|
|
||||||
Array.Copy(buffer, offset, _buffer, _position, count);
|
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count);
|
||||||
IncreasePosition(count);
|
IncreasePosition(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +150,7 @@ namespace MQTTnet.Formatter
|
|||||||
_buffer[_position] = (byte)(valueLength >> 8);
|
_buffer[_position] = (byte)(valueLength >> 8);
|
||||||
_buffer[_position + 1] = (byte)valueLength;
|
_buffer[_position + 1] = (byte)valueLength;
|
||||||
|
|
||||||
Array.Copy(value, 0, _buffer, _position + 2, valueLength);
|
MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength);
|
||||||
IncreasePosition(valueLength + 2);
|
IncreasePosition(valueLength + 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +188,7 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
_buffer[_position] = (byte)(writtenBytes >> 8);
|
_buffer[_position] = (byte)(writtenBytes >> 8);
|
||||||
_buffer[_position + 1] = (byte)writtenBytes;
|
_buffer[_position + 1] = (byte)writtenBytes;
|
||||||
|
|
||||||
IncreasePosition(writtenBytes + 2);
|
IncreasePosition(writtenBytes + 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,28 +37,28 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
public byte[] ToArray()
|
public byte[] ToArray()
|
||||||
{
|
{
|
||||||
if (Packet.Count == 0)
|
if (Payload.Count == 0)
|
||||||
{
|
{
|
||||||
return Packet.ToArray();
|
return Packet.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[Length];
|
var buffer = new byte[Length];
|
||||||
Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
||||||
Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArraySegment<byte> Join()
|
public ArraySegment<byte> Join()
|
||||||
{
|
{
|
||||||
if (Packet.Count == 0)
|
if (Payload.Count == 0)
|
||||||
{
|
{
|
||||||
return Packet;
|
return Packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buffer = new byte[Length];
|
var buffer = new byte[Length];
|
||||||
Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
|
||||||
Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
|
||||||
|
|
||||||
return new ArraySegment<byte>(buffer);
|
return new ArraySegment<byte>(buffer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,30 +4,30 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttPacketFactories
|
public static class MqttPacketFactories
|
||||||
{
|
{
|
||||||
public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
|
||||||
|
|
||||||
public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
|
||||||
|
|
||||||
public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
|
public static MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
|
||||||
|
|
||||||
public MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory();
|
public static MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory();
|
||||||
|
|
||||||
public MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory();
|
public static MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory();
|
||||||
|
|
||||||
public MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory();
|
public static MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory();
|
||||||
|
|
||||||
public MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory();
|
public static MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory();
|
||||||
|
|
||||||
public MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory();
|
public static MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory();
|
||||||
|
|
||||||
public MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory();
|
public static MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory();
|
||||||
|
|
||||||
public MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory();
|
public static MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory();
|
||||||
|
|
||||||
public MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory();
|
public static MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory();
|
||||||
|
|
||||||
public MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory();
|
public static MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,6 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
public MqttPubAckPacket Create(
|
public MqttPubAckPacket Create(
|
||||||
MqttPublishPacket publishPacket,
|
MqttPublishPacket publishPacket,
|
||||||
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
|
||||||
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
@@ -22,28 +21,19 @@ namespace MQTTnet.Formatter
|
|||||||
throw new ArgumentNullException(nameof(publishPacket));
|
throw new ArgumentNullException(nameof(publishPacket));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dispatchApplicationMessageResult == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(dispatchApplicationMessageResult));
|
||||||
|
}
|
||||||
|
|
||||||
var pubAckPacket = new MqttPubAckPacket
|
var pubAckPacket = new MqttPubAckPacket
|
||||||
{
|
{
|
||||||
PacketIdentifier = publishPacket.PacketIdentifier
|
PacketIdentifier = publishPacket.PacketIdentifier,
|
||||||
|
ReasonCode = (MqttPubAckReasonCode)dispatchApplicationMessageResult.ReasonCode,
|
||||||
|
ReasonString = dispatchApplicationMessageResult.ReasonString,
|
||||||
|
UserProperties = dispatchApplicationMessageResult.UserProperties
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
|
||||||
{
|
|
||||||
// NoMatchingSubscribers is ONLY sent by the server!
|
|
||||||
pubAckPacket.ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pubAckPacket.ReasonCode = MqttPubAckReasonCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptingPublishEventArgs != null)
|
|
||||||
{
|
|
||||||
pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
|
||||||
pubAckPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
|
|
||||||
pubAckPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubAckPacket;
|
return pubAckPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ namespace MQTTnet.Formatter
|
|||||||
return pubRecPacket;
|
return pubRecPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttPacket Create(
|
public MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||||
MqttPublishPacket publishPacket,
|
|
||||||
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
|
||||||
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
{
|
{
|
||||||
@@ -38,21 +35,11 @@ namespace MQTTnet.Formatter
|
|||||||
var pubRecPacket = new MqttPubRecPacket
|
var pubRecPacket = new MqttPubRecPacket
|
||||||
{
|
{
|
||||||
PacketIdentifier = publishPacket.PacketIdentifier,
|
PacketIdentifier = publishPacket.PacketIdentifier,
|
||||||
ReasonCode = MqttPubRecReasonCode.Success
|
ReasonCode = (MqttPubRecReasonCode)dispatchApplicationMessageResult.ReasonCode,
|
||||||
|
ReasonString = dispatchApplicationMessageResult.ReasonString,
|
||||||
|
UserProperties = dispatchApplicationMessageResult.UserProperties
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
|
||||||
{
|
|
||||||
pubRecPacket.ReasonCode = MqttPubRecReasonCode.NoMatchingSubscribers;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interceptingPublishEventArgs != null)
|
|
||||||
{
|
|
||||||
pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
|
||||||
pubRecPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
|
|
||||||
pubRecPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubRecPacket;
|
return pubRecPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.SubAck:
|
case MqttControlPacketType.SubAck:
|
||||||
return DecodeSubAckPacket(receivedMqttPacket.Body);
|
return DecodeSubAckPacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.Unsubscibe:
|
case MqttControlPacketType.Unsubscribe:
|
||||||
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
|
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.UnsubAck:
|
case MqttControlPacketType.UnsubAck:
|
||||||
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
|
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
|
||||||
@@ -108,7 +108,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
remainingLength += (uint)publishPacket.Payload.Length;
|
remainingLength += (uint)publishPacket.Payload.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(remainingLength);
|
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
||||||
|
|
||||||
var headerSize = FixedHeaderSize + remainingLengthSize;
|
var headerSize = FixedHeaderSize + remainingLengthSize;
|
||||||
var headerOffset = 5 - headerSize;
|
var headerOffset = 5 - headerSize;
|
||||||
@@ -783,7 +783,7 @@ namespace MQTTnet.Formatter.V3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
return DecodeSubscribePacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.SubAck:
|
case MqttControlPacketType.SubAck:
|
||||||
return DecodeSubAckPacket(receivedMqttPacket.Body);
|
return DecodeSubAckPacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.Unsubscibe:
|
case MqttControlPacketType.Unsubscribe:
|
||||||
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
|
return DecodeUnsubscribePacket(receivedMqttPacket.Body);
|
||||||
case MqttControlPacketType.UnsubAck:
|
case MqttControlPacketType.UnsubAck:
|
||||||
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
|
return DecodeUnsubAckPacket(receivedMqttPacket.Body);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ namespace MQTTnet.Formatter.V5
|
|||||||
{
|
{
|
||||||
public sealed class MqttV5PacketEncoder
|
public sealed class MqttV5PacketEncoder
|
||||||
{
|
{
|
||||||
|
const int FixedHeaderSize = 1;
|
||||||
|
|
||||||
readonly MqttBufferWriter _bufferWriter;
|
readonly MqttBufferWriter _bufferWriter;
|
||||||
readonly MqttV5PropertiesWriter _propertiesWriter = new MqttV5PropertiesWriter(new MqttBufferWriter(1024, 4096));
|
readonly MqttV5PropertiesWriter _propertiesWriter = new MqttV5PropertiesWriter(new MqttBufferWriter(1024, 4096));
|
||||||
|
|
||||||
@@ -40,9 +42,9 @@ namespace MQTTnet.Formatter.V5
|
|||||||
remainingLength += (uint)publishPacket.Payload.Length;
|
remainingLength += (uint)publishPacket.Payload.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(remainingLength);
|
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength);
|
||||||
|
|
||||||
var headerSize = 1 + remainingLengthSize;
|
var headerSize = FixedHeaderSize + remainingLengthSize;
|
||||||
var headerOffset = 5 - headerSize;
|
var headerOffset = 5 - headerSize;
|
||||||
|
|
||||||
// Position cursor on correct offset on beginning of array (has leading 0x0)
|
// Position cursor on correct offset on beginning of array (has leading 0x0)
|
||||||
@@ -522,7 +524,7 @@ namespace MQTTnet.Formatter.V5
|
|||||||
_bufferWriter.WriteString(topicFilter);
|
_bufferWriter.WriteString(topicFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
|
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
|
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ namespace MQTTnet.Internal
|
|||||||
_isSignaled = true;
|
_isSignaled = true;
|
||||||
|
|
||||||
Cleanup();
|
Cleanup();
|
||||||
|
|
||||||
// If there is already a waiting task let it run.
|
// If there is already a waiting task let it run.
|
||||||
if (_waiter != null)
|
if (_waiter != null)
|
||||||
{
|
{
|
||||||
_waiter.Approve();
|
_waiter.Approve();
|
||||||
|
_waiter.Dispose();
|
||||||
_waiter = null;
|
_waiter = null;
|
||||||
|
|
||||||
// Since we already got a waiter the signal must be reset right now!
|
// Since we already got a waiter the signal must be reset right now!
|
||||||
@@ -102,6 +103,8 @@ namespace MQTTnet.Internal
|
|||||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||||
CancellationTokenRegistration _cancellationTokenRegistration;
|
CancellationTokenRegistration _cancellationTokenRegistration;
|
||||||
|
|
||||||
|
volatile bool _isCompleted;
|
||||||
|
|
||||||
public AsyncSignalWaiter(CancellationToken cancellationToken)
|
public AsyncSignalWaiter(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (cancellationToken.CanBeCanceled)
|
if (cancellationToken.CanBeCanceled)
|
||||||
@@ -114,18 +117,37 @@ namespace MQTTnet.Internal
|
|||||||
|
|
||||||
public void Approve()
|
public void Approve()
|
||||||
{
|
{
|
||||||
|
if (_isCompleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isCompleted = true;
|
||||||
_promise.TrySetResult(true);
|
_promise.TrySetResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancellationTokenRegistration.Dispose();
|
_cancellationTokenRegistration.Dispose();
|
||||||
|
|
||||||
|
if (_isCompleted)
|
||||||
|
{
|
||||||
|
// Avoid allocation of _ObjectDisposedException_ which may not be used.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isCompleted = true;
|
||||||
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter)));
|
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cancel()
|
void Cancel()
|
||||||
{
|
{
|
||||||
|
if (_isCompleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isCompleted = true;
|
||||||
_promise.TrySetCanceled();
|
_promise.TrySetCanceled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace MQTTnet.Internal
|
||||||
|
{
|
||||||
|
public static class CancellationTokenSourceExtensions
|
||||||
|
{
|
||||||
|
public static bool TryCancel(this CancellationTokenSource cancellationTokenSource, bool throwOnFirstException = false)
|
||||||
|
{
|
||||||
|
if (cancellationTokenSource == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Checking the _IsCancellationRequested_ here will not throw an
|
||||||
|
// "ObjectDisposedException" as the getter of the property "Token" will do!
|
||||||
|
if (cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationTokenSource.Cancel(throwOnFirstException);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace MQTTnet.Internal
|
||||||
|
{
|
||||||
|
public static class MqttMemoryHelper
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static void Copy(byte[] source, int sourceIndex, byte[] destination, int destinationIndex, int length)
|
||||||
|
{
|
||||||
|
#if NETCOREAPP3_1_OR_GREATER || NETSTANDARD2_1
|
||||||
|
source.AsSpan(sourceIndex, length).CopyTo(destination.AsSpan(destinationIndex, length));
|
||||||
|
#elif NET461_OR_GREATER || NETSTANDARD1_3_OR_GREATER
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* sourceHandle = &source[sourceIndex])
|
||||||
|
{
|
||||||
|
fixed (byte* destinationHandle = &destination[destinationIndex])
|
||||||
|
{
|
||||||
|
System.Buffer.MemoryCopy(sourceHandle, destinationHandle, length, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Array.Copy(source, sourceIndex, destination, destinationIndex, length);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,8 @@
|
|||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
|
||||||
|
|||||||
@@ -5,78 +5,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
// ReSharper disable MemberCanBePrivate.Global
|
|
||||||
|
|
||||||
namespace MQTTnet
|
namespace MQTTnet
|
||||||
{
|
{
|
||||||
public static class MqttApplicationMessageExtensions
|
public static class MqttApplicationMessageExtensions
|
||||||
{
|
{
|
||||||
public static short ConvertPayloadToByte(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Payload.Length != 1)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Payload requires to be 1 bytes long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return applicationMessage.Payload[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static short ConvertPayloadToInt16(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Payload.Length != 2)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Payload requires to be 2 bytes long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return BitConverter.ToInt16(applicationMessage.Payload, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int ConvertPayloadToInt32(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Payload?.Length != 4)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Payload requires to be 4 bytes long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return BitConverter.ToInt32(applicationMessage.Payload, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long ConvertPayloadToInt64(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Payload.Length != 8)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Payload requires to be 8 bytes long.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return BitConverter.ToInt64(applicationMessage.Payload, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
public static string ConvertPayloadToString(this MqttApplicationMessage applicationMessage)
|
||||||
{
|
{
|
||||||
if (applicationMessage == null)
|
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationMessage.Payload == null)
|
if (applicationMessage.Payload == null)
|
||||||
{
|
{
|
||||||
@@ -90,65 +25,5 @@ namespace MQTTnet
|
|||||||
|
|
||||||
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
|
return Encoding.UTF8.GetString(applicationMessage.Payload, 0, applicationMessage.Payload.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static decimal ParsePayloadToDecimal(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return decimal.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static double ParsePayloadToDouble(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return double.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static short ParsePayloadToInt16(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return short.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int ParsePayloadToInt32(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return int.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long ParsePayloadToInt64(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return long.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float ParsePayloadToSingle(this MqttApplicationMessage applicationMessage)
|
|
||||||
{
|
|
||||||
if (applicationMessage == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(applicationMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
return float.Parse(ConvertPayloadToString(applicationMessage));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace MQTTnet.Packets
|
||||||
|
{
|
||||||
|
public static class MqttPacketExtensions
|
||||||
|
{
|
||||||
|
public static string GetRfcName(this MqttPacket packet)
|
||||||
|
{
|
||||||
|
if (packet == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (packet)
|
||||||
|
{
|
||||||
|
case MqttConnectPacket _:
|
||||||
|
{
|
||||||
|
return "CONNECT";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttConnAckPacket _:
|
||||||
|
{
|
||||||
|
return "CONNACK";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttAuthPacket _:
|
||||||
|
{
|
||||||
|
return "AUTH";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttDisconnectPacket _:
|
||||||
|
{
|
||||||
|
return "DISCONNECT";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPingReqPacket _:
|
||||||
|
{
|
||||||
|
return "PINGREQ";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPingRespPacket _:
|
||||||
|
{
|
||||||
|
return "PINGRESP";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttSubscribePacket _:
|
||||||
|
{
|
||||||
|
return "SUBSCRIBE";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttSubAckPacket _:
|
||||||
|
{
|
||||||
|
return "SUBACK";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttUnsubscribePacket _:
|
||||||
|
{
|
||||||
|
return "UNSUBSCRIBE";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttUnsubAckPacket _:
|
||||||
|
{
|
||||||
|
return "UNSUBACK";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPublishPacket _:
|
||||||
|
{
|
||||||
|
return "PUBLISH";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPubAckPacket _:
|
||||||
|
{
|
||||||
|
return "PUBACK";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPubRelPacket _:
|
||||||
|
{
|
||||||
|
return "PUBREL";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPubRecPacket _:
|
||||||
|
{
|
||||||
|
return "PUBREC";
|
||||||
|
}
|
||||||
|
|
||||||
|
case MqttPubCompPacket _:
|
||||||
|
{
|
||||||
|
return "PUBCOMP";
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return packet.GetType().Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Protocol
|
|||||||
PubComp = 7,
|
PubComp = 7,
|
||||||
Subscribe = 8,
|
Subscribe = 8,
|
||||||
SubAck = 9,
|
SubAck = 9,
|
||||||
Unsubscibe = 10,
|
Unsubscribe = 10,
|
||||||
UnsubAck = 11,
|
UnsubAck = 11,
|
||||||
PingReq = 12,
|
PingReq = 12,
|
||||||
PingResp = 13,
|
PingResp = 13,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
ApplicationMessage = applicationMessage ?? throw new ArgumentNullException(nameof(applicationMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SenderClientId { get; set; }
|
public string SenderClientId { get; set; } = string.Empty;
|
||||||
|
|
||||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,27 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class DispatchApplicationMessageResult
|
public sealed class DispatchApplicationMessageResult
|
||||||
{
|
{
|
||||||
public DispatchApplicationMessageResult(int matchingSubscribersCount)
|
public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List<MqttUserProperty> userProperties)
|
||||||
{
|
{
|
||||||
MatchingSubscribersCount = matchingSubscribersCount;
|
ReasonCode = reasonCode;
|
||||||
|
CloseConnection = closeConnection;
|
||||||
|
ReasonString = reasonString;
|
||||||
|
UserProperties = userProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MatchingSubscribersCount { get; }
|
public bool CloseConnection { get; }
|
||||||
|
|
||||||
|
public int ReasonCode { get; }
|
||||||
|
|
||||||
|
public string ReasonString { get; }
|
||||||
|
|
||||||
|
public List<MqttUserProperty> UserProperties { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,27 +12,21 @@ using MQTTnet.Diagnostics;
|
|||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.PacketDispatcher;
|
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
public sealed class MqttClient
|
public sealed class MqttClient : IDisposable
|
||||||
{
|
{
|
||||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
|
||||||
readonly MqttConnectPacket _connectPacket;
|
readonly MqttConnectPacket _connectPacket;
|
||||||
readonly MqttServerEventContainer _eventContainer;
|
readonly MqttServerEventContainer _eventContainer;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
|
||||||
|
|
||||||
readonly MqttServerOptions _serverOptions;
|
readonly MqttServerOptions _serverOptions;
|
||||||
|
|
||||||
readonly MqttClientSessionsManager _sessionsManager;
|
readonly MqttClientSessionsManager _sessionsManager;
|
||||||
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
|
||||||
|
|
||||||
CancellationTokenSource _cancellationToken;
|
CancellationTokenSource _cancellationToken = new CancellationTokenSource();
|
||||||
bool _disconnectPacketSent;
|
bool _disconnectPacketSent;
|
||||||
|
|
||||||
public MqttClient(
|
public MqttClient(
|
||||||
@@ -45,14 +39,13 @@ namespace MQTTnet.Server
|
|||||||
IMqttNetLogger logger)
|
IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
|
||||||
_eventContainer = eventContainer;
|
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer));
|
||||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||||
|
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
||||||
|
|
||||||
ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
ChannelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
|
||||||
Endpoint = channelAdapter.Endpoint;
|
Endpoint = channelAdapter.Endpoint;
|
||||||
|
|
||||||
Session = session ?? throw new ArgumentNullException(nameof(session));
|
Session = session ?? throw new ArgumentNullException(nameof(session));
|
||||||
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
|
|
||||||
|
|
||||||
if (logger == null)
|
if (logger == null)
|
||||||
{
|
{
|
||||||
@@ -80,53 +73,56 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
public MqttClientStatistics Statistics { get; } = new MqttClientStatistics();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cancellationToken?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetStatistics()
|
public void ResetStatistics()
|
||||||
{
|
{
|
||||||
ChannelAdapter.ResetStatistics();
|
ChannelAdapter.ResetStatistics();
|
||||||
|
Statistics.ResetStatistics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync()
|
public async Task RunAsync()
|
||||||
{
|
{
|
||||||
_logger.Info("Client '{0}': Session started.", Id);
|
_logger.Info("Client '{0}': Session started", Id);
|
||||||
|
|
||||||
Session.LatestConnectPacket = _connectPacket;
|
Session.LatestConnectPacket = _connectPacket;
|
||||||
Session.WillMessageSent = false;
|
Session.WillMessageSent = false;
|
||||||
|
|
||||||
using (_cancellationToken = new CancellationTokenSource())
|
try
|
||||||
{
|
{
|
||||||
var cancellationToken = _cancellationToken.Token;
|
var cancellationToken = _cancellationToken.Token;
|
||||||
|
|
||||||
try
|
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default)
|
||||||
{
|
.ConfigureAwait(false);
|
||||||
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default).ConfigureAwait(false);
|
|
||||||
|
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
|
|
||||||
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
await ReceivePackagesLoop(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsRunning = false;
|
|
||||||
|
|
||||||
_cancellationToken?.Cancel();
|
|
||||||
_cancellationToken = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
_packetDispatcher.CancelAll();
|
_cancellationToken?.TryCancel();
|
||||||
|
_cancellationToken?.Dispose();
|
||||||
|
_cancellationToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
|
if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
|
||||||
{
|
{
|
||||||
var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
|
var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket);
|
||||||
var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
|
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket);
|
||||||
|
|
||||||
_ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
|
_ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None);
|
||||||
Session.WillMessageSent = true;
|
Session.WillMessageSent = true;
|
||||||
|
|
||||||
_logger.Info("Client '{0}': Published will message.", Id);
|
_logger.Info("Client '{0}': Published will message", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Client '{0}': Connection stopped.", Id);
|
_logger.Info("Client '{0}': Connection stopped", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
@@ -180,6 +176,12 @@ namespace MQTTnet.Server
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HandleIncomingPingReqPacket()
|
||||||
|
{
|
||||||
|
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
|
||||||
|
Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
|
||||||
|
}
|
||||||
|
|
||||||
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
|
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
|
||||||
{
|
{
|
||||||
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
var acknowledgedPublishPacket = Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
|
||||||
@@ -208,41 +210,17 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
HandleTopicAlias(publishPacket);
|
HandleTopicAlias(publishPacket);
|
||||||
|
|
||||||
InterceptingPublishEventArgs interceptingPublishEventArgs = null;
|
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
|
||||||
var closeConnection = false;
|
|
||||||
var processPublish = true;
|
|
||||||
|
|
||||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
var dispatchApplicationMessageResult =
|
||||||
{
|
await _sessionsManager.DispatchApplicationMessage(Id, Session.Items, applicationMessage, cancellationToken).ConfigureAwait(false);
|
||||||
interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, Id, Session.Items);
|
|
||||||
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
|
||||||
{
|
|
||||||
// This can happen if a topic alias us used but the topic is
|
|
||||||
// unknown to the server.
|
|
||||||
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
|
||||||
interceptingPublishEventArgs.ProcessPublish = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
if (dispatchApplicationMessageResult.CloseConnection)
|
||||||
|
|
||||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
|
||||||
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
|
||||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closeConnection)
|
|
||||||
{
|
{
|
||||||
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
await StopAsync(MqttDisconnectReasonCode.UnspecifiedError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchApplicationMessageResult dispatchResult = null;
|
|
||||||
if (processPublish && applicationMessage != null)
|
|
||||||
{
|
|
||||||
dispatchResult = await _sessionsManager.DispatchApplicationMessage(Id, applicationMessage).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (publishPacket.QualityOfServiceLevel)
|
switch (publishPacket.QualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||||
@@ -252,19 +230,19 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||||
{
|
{
|
||||||
var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
|
var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, dispatchApplicationMessageResult);
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||||
{
|
{
|
||||||
var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
|
var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, dispatchApplicationMessageResult);
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRecPacket));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
throw new MqttCommunicationException("Received a not supported QoS level");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,7 +251,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
// Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished
|
// Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished
|
||||||
// properly when the client has sent the PUBCOMP packet.
|
// properly when the client has sent the PUBCOMP packet.
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
@@ -281,7 +259,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +267,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
|
var subAckPacket = MqttPacketFactories.SubAck.Create(subscribePacket, subscribeResult);
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
|
||||||
|
|
||||||
@@ -303,7 +281,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
|
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
|
||||||
{
|
{
|
||||||
var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
|
var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch);
|
||||||
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,7 +291,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
var unsubAckPacket = MqttPacketFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
|
||||||
|
|
||||||
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
|
||||||
|
|
||||||
@@ -344,7 +322,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Warning("Client '{0}': Received invalid topic alias ({1}).", Id, publishPacket.TopicAlias);
|
_logger.Warning("Client '{0}': Received invalid topic alias ({1})", Id, publishPacket.TopicAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,6 +348,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
|
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
MqttPacket currentPacket = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// We do not listen for the cancellation token here because the internal buffer might still
|
// We do not listen for the cancellation token here because the internal buffer might still
|
||||||
@@ -379,12 +358,12 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
currentPacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
|
||||||
if (packet == null)
|
if (currentPacket == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for cancellation again because receive packet might block some time.
|
// Check for cancellation again because receive packet might block some time.
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -397,73 +376,69 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var processPacket = true;
|
var processPacket = true;
|
||||||
|
|
||||||
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
|
var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, currentPacket, Session.Items);
|
||||||
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
|
||||||
packet = interceptingPacketEventArgs.Packet;
|
currentPacket = interceptingPacketEventArgs.Packet;
|
||||||
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
processPacket = interceptingPacketEventArgs.ProcessPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!processPacket || packet == null)
|
if (!processPacket || currentPacket == null)
|
||||||
{
|
{
|
||||||
// Restart the receiving process to get the next packet ignoring the current one..
|
// Restart the receiving process to get the next packet ignoring the current one..
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Statistics.HandleReceivedPacket(packet);
|
Statistics.HandleReceivedPacket(currentPacket);
|
||||||
|
|
||||||
if (packet is MqttPublishPacket publishPacket)
|
if (currentPacket is MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
|
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubAckPacket pubAckPacket)
|
else if (currentPacket is MqttPubAckPacket pubAckPacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
|
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubCompPacket pubCompPacket)
|
else if (currentPacket is MqttPubCompPacket pubCompPacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
|
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRecPacket pubRecPacket)
|
else if (currentPacket is MqttPubRecPacket pubRecPacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
|
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPubRelPacket pubRelPacket)
|
else if (currentPacket is MqttPubRelPacket pubRelPacket)
|
||||||
{
|
{
|
||||||
HandleIncomingPubRelPacket(pubRelPacket);
|
HandleIncomingPubRelPacket(pubRelPacket);
|
||||||
}
|
}
|
||||||
else if (packet is MqttSubscribePacket subscribePacket)
|
else if (currentPacket is MqttSubscribePacket subscribePacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
|
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttUnsubscribePacket unsubscribePacket)
|
else if (currentPacket is MqttUnsubscribePacket unsubscribePacket)
|
||||||
{
|
{
|
||||||
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (packet is MqttPingReqPacket)
|
else if (currentPacket is MqttPingReqPacket)
|
||||||
{
|
{
|
||||||
// See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
|
HandleIncomingPingReqPacket();
|
||||||
Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
|
|
||||||
}
|
}
|
||||||
else if (packet is MqttPingRespPacket)
|
else if (currentPacket is MqttPingRespPacket)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
|
throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
|
||||||
}
|
}
|
||||||
else if (packet is MqttDisconnectPacket)
|
else if (currentPacket is MqttDisconnectPacket)
|
||||||
{
|
{
|
||||||
IsCleanDisconnect = true;
|
IsCleanDisconnect = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!_packetDispatcher.TryDispatch(packet))
|
throw new MqttProtocolViolationException("Packet not allowed");
|
||||||
{
|
|
||||||
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,11 +449,25 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (exception is MqttCommunicationException)
|
if (exception is MqttCommunicationException)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", Id);
|
_logger.Warning(exception, "Client '{0}': Communication exception while receiving packets", Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logLevel = MqttNetLogLevel.Error;
|
||||||
|
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
// There was an exception but the connection is already closed. So there is no chance to send a response to the client etc.
|
||||||
|
logLevel = MqttNetLogLevel.Warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPacket == null)
|
||||||
|
{
|
||||||
|
_logger.Publish(logLevel, exception, "Client '{0}': Error while receiving packets", Id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id);
|
_logger.Publish(logLevel, exception, "Client '{0}': Error while processing {1} packet", Id, currentPacket.GetRfcName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -530,15 +519,15 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (exception is MqttCommunicationTimedOutException)
|
if (exception is MqttCommunicationTimedOutException)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", Id);
|
_logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to timeout", Id);
|
||||||
}
|
}
|
||||||
else if (exception is MqttCommunicationException)
|
else if (exception is MqttCommunicationException)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", Id);
|
_logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to communication exception", Id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Client '{0}': Sending publish packet failed.", Id);
|
_logger.Error(exception, "Client '{0}': Sending PUBLISH packet failed", Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packetBusItem?.Packet is MqttPublishPacket publishPacket)
|
if (packetBusItem?.Packet is MqttPublishPacket publishPacket)
|
||||||
@@ -556,15 +545,7 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
void StopInternal()
|
void StopInternal()
|
||||||
{
|
{
|
||||||
try
|
_cancellationToken?.TryCancel();
|
||||||
{
|
|
||||||
_cancellationToken?.Cancel();
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
// This can happen when connections are created and dropped very quickly.
|
|
||||||
// It is not an issue if the cancellation token cannot be canceled multiple times.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
async Task TrySendDisconnectPacket(MqttDisconnectReasonCode reasonCode)
|
||||||
@@ -573,8 +554,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
// This also indicates that it was tried at least!
|
// This also indicates that it was tried at least!
|
||||||
_disconnectPacketSent = true;
|
_disconnectPacketSent = true;
|
||||||
|
|
||||||
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
|
var disconnectPacket = MqttPacketFactories.Disconnect.Create(reasonCode);
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
|
||||||
{
|
{
|
||||||
@@ -583,7 +564,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1}).", Id, reasonCode);
|
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet (ReasonCode = {1})", Id, reasonCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ namespace MQTTnet.Server
|
|||||||
readonly MqttServerEventContainer _eventContainer;
|
readonly MqttServerEventContainer _eventContainer;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
|
||||||
|
|
||||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
@@ -127,81 +126,118 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
_logger.Verbose("Session for client '{0}' deleted.", clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(string senderId, MqttApplicationMessage applicationMessage)
|
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(
|
||||||
|
string senderId,
|
||||||
|
IDictionary senderSessionItems,
|
||||||
|
MqttApplicationMessage applicationMessage,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var matchingSubscribersCount = 0;
|
var processPublish = true;
|
||||||
try
|
var closeConnection = false;
|
||||||
|
string reasonString = null;
|
||||||
|
List<MqttUserProperty> userProperties = null;
|
||||||
|
var reasonCode = 0; // The reason code is later converted into several different but compatible enums!
|
||||||
|
|
||||||
|
// Allow the user to intercept application message...
|
||||||
|
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
if (applicationMessage.Retain)
|
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems);
|
||||||
|
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
|
||||||
{
|
{
|
||||||
await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
|
// This can happen if a topic alias us used but the topic is
|
||||||
}
|
// unknown to the server.
|
||||||
|
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
|
||||||
List<MqttSession> subscriberSessions;
|
interceptingPublishEventArgs.ProcessPublish = false;
|
||||||
lock (_sessionsManagementLock)
|
|
||||||
{
|
|
||||||
subscriberSessions = _subscriberSessions.ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate application message topic hash once for subscription checks
|
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
||||||
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
|
|
||||||
|
|
||||||
foreach (var session in subscriberSessions)
|
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
||||||
{
|
closeConnection = interceptingPublishEventArgs.CloseConnection;
|
||||||
if(!session.TryCheckSubscriptions(
|
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
||||||
applicationMessage.Topic,
|
reasonString = interceptingPublishEventArgs.Response.ReasonString;
|
||||||
topicHash,
|
userProperties = interceptingPublishEventArgs.Response.UserProperties;
|
||||||
applicationMessage.QualityOfServiceLevel,
|
reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||||
senderId,
|
|
||||||
out var checkSubscriptionsResult))
|
|
||||||
{
|
|
||||||
// Checking the subscriptions has failed for the session. The session
|
|
||||||
// will be ignored.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkSubscriptionsResult.IsSubscribed)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPublishPacket = _packetFactories.Publish.Create(applicationMessage);
|
|
||||||
newPublishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
|
|
||||||
newPublishPacket.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
|
|
||||||
|
|
||||||
if (newPublishPacket.QualityOfServiceLevel > 0)
|
|
||||||
{
|
|
||||||
newPublishPacket.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkSubscriptionsResult.RetainAsPublished)
|
|
||||||
{
|
|
||||||
// Transfer the original retain state from the publisher. This is a MQTTv5 feature.
|
|
||||||
newPublishPacket.Retain = applicationMessage.Retain;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newPublishPacket.Retain = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.EnqueueDataPacket(new MqttPacketBusItem(newPublishPacket));
|
|
||||||
matchingSubscribersCount++;
|
|
||||||
|
|
||||||
_logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingSubscribersCount == 0)
|
|
||||||
{
|
|
||||||
await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DispatchApplicationMessageResult(matchingSubscribersCount);
|
// Process the application message...
|
||||||
|
if (processPublish && applicationMessage != null)
|
||||||
|
{
|
||||||
|
var matchingSubscribersCount = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (applicationMessage.Retain)
|
||||||
|
{
|
||||||
|
await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MqttSession> subscriberSessions;
|
||||||
|
lock (_sessionsManagementLock)
|
||||||
|
{
|
||||||
|
subscriberSessions = _subscriberSessions.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate application message topic hash once for subscription checks
|
||||||
|
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
|
||||||
|
|
||||||
|
foreach (var session in subscriberSessions)
|
||||||
|
{
|
||||||
|
if (!session.TryCheckSubscriptions(
|
||||||
|
applicationMessage.Topic,
|
||||||
|
topicHash,
|
||||||
|
applicationMessage.QualityOfServiceLevel,
|
||||||
|
senderId,
|
||||||
|
out var checkSubscriptionsResult))
|
||||||
|
{
|
||||||
|
// Checking the subscriptions has failed for the session. The session
|
||||||
|
// will be ignored.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkSubscriptionsResult.IsSubscribed)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var publishPacketCopy = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||||
|
publishPacketCopy.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
|
||||||
|
publishPacketCopy.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
|
||||||
|
|
||||||
|
if (publishPacketCopy.QualityOfServiceLevel > 0)
|
||||||
|
{
|
||||||
|
publishPacketCopy.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkSubscriptionsResult.RetainAsPublished)
|
||||||
|
{
|
||||||
|
// Transfer the original retain state from the publisher. This is a MQTTv5 feature.
|
||||||
|
publishPacketCopy.Retain = applicationMessage.Retain;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
publishPacketCopy.Retain = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.EnqueueDataPacket(new MqttPacketBusItem(publishPacketCopy));
|
||||||
|
matchingSubscribersCount++;
|
||||||
|
|
||||||
|
_logger.Verbose("Client '{0}': Queued PUBLISH packet with topic '{1}'.", session.Id, applicationMessage.Topic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingSubscribersCount == 0)
|
||||||
|
{
|
||||||
|
reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers;
|
||||||
|
await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
_logger.Error(exception, "Unhandled exception while processing next queued application message.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DispatchApplicationMessageResult(reasonCode, closeConnection, reasonString, userProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -288,7 +324,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
|
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
|
||||||
var connAckPacket = _packetFactories.ConnAck.Create(validatingConnectionEventArgs);
|
var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs);
|
||||||
|
|
||||||
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
|
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
|
||||||
{
|
{
|
||||||
@@ -430,7 +466,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
|
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
|
||||||
{
|
{
|
||||||
var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
|
var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch);
|
||||||
clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,11 +532,11 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
MqttSession oldSession;
|
MqttSession oldSession;
|
||||||
MqttClient oldClient;
|
MqttClient oldClient;
|
||||||
|
|
||||||
lock (_sessionsManagementLock)
|
lock (_sessionsManagementLock)
|
||||||
{
|
{
|
||||||
MqttSession session;
|
MqttSession session;
|
||||||
|
|
||||||
// Create a new session (if required).
|
// Create a new session (if required).
|
||||||
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
|
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
|
||||||
{
|
{
|
||||||
@@ -519,7 +555,7 @@ namespace MQTTnet.Server
|
|||||||
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||||
session = oldSession;
|
session = oldSession;
|
||||||
oldSession = null;
|
oldSession = null;
|
||||||
|
|
||||||
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
|
||||||
session.IsPersistent = sessionShouldPersist;
|
session.IsPersistent = sessionShouldPersist;
|
||||||
connAckPacket.IsSessionPresent = true;
|
connAckPacket.IsSessionPresent = true;
|
||||||
@@ -528,7 +564,7 @@ namespace MQTTnet.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
_sessions[connectPacket.ClientId] = session;
|
_sessions[connectPacket.ClientId] = session;
|
||||||
|
|
||||||
// Create a new client (always required).
|
// Create a new client (always required).
|
||||||
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
|
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
|
||||||
if (oldClient != null)
|
if (oldClient != null)
|
||||||
@@ -537,7 +573,7 @@ namespace MQTTnet.Server
|
|||||||
// for a later DISCONNECT packet.
|
// for a later DISCONNECT packet.
|
||||||
oldClient.IsTakenOver = true;
|
oldClient.IsTakenOver = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
client = CreateClient(connectPacket, channelAdapter, session);
|
client = CreateClient(connectPacket, channelAdapter, session);
|
||||||
|
|
||||||
_clients[connectPacket.ClientId] = client;
|
_clients[connectPacket.ClientId] = client;
|
||||||
@@ -549,19 +585,15 @@ namespace MQTTnet.Server
|
|||||||
var preparingSessionEventArgs = new PreparingSessionEventArgs();
|
var preparingSessionEventArgs = new PreparingSessionEventArgs();
|
||||||
await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false);
|
await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldClient != null)
|
if (oldClient != null)
|
||||||
{
|
{
|
||||||
await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
|
||||||
{
|
{
|
||||||
var eventArgs = new ClientDisconnectedEventArgs(
|
var eventArgs = new ClientDisconnectedEventArgs(oldClient.Id, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items);
|
||||||
oldClient.Id,
|
|
||||||
MqttClientDisconnectType.Takeover,
|
|
||||||
oldClient.Endpoint,
|
|
||||||
oldClient.Session.Items);
|
|
||||||
|
|
||||||
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientStatistics
|
public sealed class MqttClientStatistics
|
||||||
{
|
{
|
||||||
|
long _receivedApplicationMessagesCount;
|
||||||
|
|
||||||
// Start with 1 because the CONNACK packet is not counted here.
|
// Start with 1 because the CONNACK packet is not counted here.
|
||||||
long _receivedPacketsCount = 1;
|
long _receivedPacketsCount = 1;
|
||||||
|
long _sentApplicationMessagesCount;
|
||||||
|
|
||||||
// Start with 1 because the CONNECT packet is not counted here.
|
// Start with 1 because the CONNECT packet is not counted here.
|
||||||
long _sentPacketsCount = 1;
|
long _sentPacketsCount = 1;
|
||||||
|
|
||||||
long _receivedApplicationMessagesCount;
|
|
||||||
long _sentApplicationMessagesCount;
|
|
||||||
|
|
||||||
public MqttClientStatistics()
|
public MqttClientStatistics()
|
||||||
{
|
{
|
||||||
ConnectedTimestamp = DateTime.UtcNow;
|
ConnectedTimestamp = DateTime.UtcNow;
|
||||||
@@ -28,36 +28,36 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp;
|
LastNonKeepAlivePacketReceivedTimestamp = ConnectedTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime ConnectedTimestamp { get; }
|
public DateTime ConnectedTimestamp { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LastPacketReceivedTimestamp { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
|
|
||||||
/// </summary>
|
|
||||||
public DateTime LastPacketSentTimestamp { get; private set; }
|
|
||||||
|
|
||||||
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
|
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
|
||||||
|
|
||||||
public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
|
/// <summary>
|
||||||
|
/// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastPacketReceivedTimestamp { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastPacketSentTimestamp { get; private set; }
|
||||||
|
|
||||||
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount);
|
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount);
|
||||||
|
|
||||||
public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
|
|
||||||
|
|
||||||
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
|
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
|
||||||
|
|
||||||
|
public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
|
||||||
|
|
||||||
|
public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
|
||||||
|
|
||||||
public void HandleReceivedPacket(MqttPacket packet)
|
public void HandleReceivedPacket(MqttPacket packet)
|
||||||
{
|
{
|
||||||
if (packet == null)
|
if (packet == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This class is tracking all values from Clients perspective!
|
// This class is tracking all values from Clients perspective!
|
||||||
LastPacketSentTimestamp = DateTime.UtcNow;
|
LastPacketSentTimestamp = DateTime.UtcNow;
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This class is tracking all values from Clients perspective!
|
// This class is tracking all values from Clients perspective!
|
||||||
LastPacketReceivedTimestamp = DateTime.UtcNow;
|
LastPacketReceivedTimestamp = DateTime.UtcNow;
|
||||||
|
|
||||||
@@ -91,5 +91,13 @@ namespace MQTTnet.Server
|
|||||||
Interlocked.Increment(ref _receivedApplicationMessagesCount);
|
Interlocked.Increment(ref _receivedApplicationMessagesCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ResetStatistics()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _sentApplicationMessagesCount, 0);
|
||||||
|
Interlocked.Exchange(ref _receivedApplicationMessagesCount, 0);
|
||||||
|
Interlocked.Exchange(ref _sentPacketsCount, 0);
|
||||||
|
Interlocked.Exchange(ref _receivedPacketsCount, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,18 +55,21 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
|
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
|
||||||
{
|
{
|
||||||
possibleSubscriptions.AddRange(noWildcardSubscriptions.ToList());
|
possibleSubscriptions.AddRange(noWildcardSubscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var wcs in _wildcardSubscriptionsByTopicHash)
|
foreach (var wcs in _wildcardSubscriptionsByTopicHash)
|
||||||
{
|
{
|
||||||
var wildcardSubscriptions = wcs.Value;
|
|
||||||
var subscriptionHash = wcs.Key;
|
var subscriptionHash = wcs.Key;
|
||||||
var subscriptionHashMask = wildcardSubscriptions.HashMask;
|
var subscriptionsByHashMask = wcs.Value.SubscriptionsByHashMask;
|
||||||
|
foreach (var shm in subscriptionsByHashMask)
|
||||||
if ((topicHash & subscriptionHashMask) == subscriptionHash)
|
|
||||||
{
|
{
|
||||||
possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList());
|
var subscriptionHashMask = shm.Key;
|
||||||
|
if ((topicHash & subscriptionHashMask) == subscriptionHash)
|
||||||
|
{
|
||||||
|
var subscriptions = shm.Value;
|
||||||
|
possibleSubscriptions.AddRange(subscriptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,8 +268,8 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
||||||
{
|
{
|
||||||
subscriptions.Subscriptions.Remove(existingSubscription);
|
subscriptions.RemoveSubscription(existingSubscription);
|
||||||
if (subscriptions.Subscriptions.Count == 0)
|
if (subscriptions.SubscriptionsByHashMask.Count == 0)
|
||||||
{
|
{
|
||||||
_wildcardSubscriptionsByTopicHash.Remove(topicHash);
|
_wildcardSubscriptionsByTopicHash.Remove(topicHash);
|
||||||
}
|
}
|
||||||
@@ -346,9 +349,9 @@ namespace MQTTnet.Server
|
|||||||
// must remove object from topic hash dictionary first
|
// must remove object from topic hash dictionary first
|
||||||
if (hasWildcard)
|
if (hasWildcard)
|
||||||
{
|
{
|
||||||
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subs))
|
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
||||||
{
|
{
|
||||||
subs.Subscriptions.Remove(existingSubscription);
|
subscriptions.RemoveSubscription(existingSubscription);
|
||||||
// no need to remove empty entry because we'll be adding subscription again below
|
// no need to remove empty entry because we'll be adding subscription again below
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,11 +373,11 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
|
||||||
{
|
{
|
||||||
subscriptions = new TopicHashMaskSubscriptions(topicHashMask);
|
subscriptions = new TopicHashMaskSubscriptions();
|
||||||
_wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
|
_wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptions.Subscriptions.Add(subscription);
|
subscriptions.AddSubscription(subscription);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,17 +3,32 @@ using System.Collections.Generic;
|
|||||||
namespace MQTTnet.Server
|
namespace MQTTnet.Server
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper class that stores the topic hash mask common to all contained Subscriptions for direct access.
|
/// Helper class that stores subscriptions by their topic hash mask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class TopicHashMaskSubscriptions
|
public sealed class TopicHashMaskSubscriptions
|
||||||
{
|
{
|
||||||
public TopicHashMaskSubscriptions(ulong hashMask)
|
public Dictionary<ulong, HashSet<MqttSubscription>> SubscriptionsByHashMask { get; } = new Dictionary<ulong, HashSet<MqttSubscription>>();
|
||||||
|
|
||||||
|
public void AddSubscription(MqttSubscription subscription)
|
||||||
{
|
{
|
||||||
HashMask = hashMask;
|
if (!SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions))
|
||||||
|
{
|
||||||
|
subscriptions = new HashSet<MqttSubscription>();
|
||||||
|
SubscriptionsByHashMask.Add(subscription.TopicHashMask, subscriptions);
|
||||||
|
}
|
||||||
|
subscriptions.Add(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong HashMask { get; }
|
public void RemoveSubscription(MqttSubscription subscription)
|
||||||
|
{
|
||||||
public HashSet<MqttSubscription> Subscriptions { get; } = new HashSet<MqttSubscription>();
|
if (SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions))
|
||||||
|
{
|
||||||
|
subscriptions.Remove(subscription);
|
||||||
|
if (subscriptions.Count == 0)
|
||||||
|
{
|
||||||
|
SubscriptionsByHashMask.Remove(subscription.TopicHashMask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,17 +19,17 @@ namespace MQTTnet.Server
|
|||||||
{
|
{
|
||||||
public class MqttServer : Disposable
|
public class MqttServer : Disposable
|
||||||
{
|
{
|
||||||
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
|
||||||
|
|
||||||
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
|
||||||
readonly ICollection<IMqttServerAdapter> _adapters;
|
readonly ICollection<IMqttServerAdapter> _adapters;
|
||||||
readonly MqttClientSessionsManager _clientSessionsManager;
|
readonly MqttClientSessionsManager _clientSessionsManager;
|
||||||
|
readonly MqttServerEventContainer _eventContainer = new MqttServerEventContainer();
|
||||||
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
readonly MqttServerKeepAliveMonitor _keepAliveMonitor;
|
||||||
readonly MqttNetSourceLogger _logger;
|
readonly MqttNetSourceLogger _logger;
|
||||||
readonly MqttServerOptions _options;
|
readonly MqttServerOptions _options;
|
||||||
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
|
readonly IDictionary _sessionItems = new ConcurrentDictionary<object, object>();
|
||||||
|
|
||||||
CancellationTokenSource _cancellationTokenSource;
|
CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
public MqttServer(MqttServerOptions options, IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||||
@@ -207,7 +207,7 @@ namespace MQTTnet.Server
|
|||||||
return _clientSessionsManager.GetSessionStatusAsync();
|
return _clientSessionsManager.GetSessionStatusAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
|
public Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (injectedApplicationMessage == null)
|
if (injectedApplicationMessage == null)
|
||||||
{
|
{
|
||||||
@@ -223,34 +223,16 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
ThrowIfNotStarted();
|
ThrowIfNotStarted();
|
||||||
|
|
||||||
var processPublish = true;
|
if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic))
|
||||||
var applicationMessage = injectedApplicationMessage.ApplicationMessage;
|
|
||||||
|
|
||||||
if (_eventContainer.InterceptingPublishEvent.HasHandlers)
|
|
||||||
{
|
|
||||||
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(
|
|
||||||
applicationMessage,
|
|
||||||
_cancellationTokenSource.Token,
|
|
||||||
injectedApplicationMessage.SenderClientId,
|
|
||||||
_sessionItems);
|
|
||||||
|
|
||||||
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
|
|
||||||
|
|
||||||
applicationMessage = interceptingPublishEventArgs.ApplicationMessage;
|
|
||||||
processPublish = interceptingPublishEventArgs.ProcessPublish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!processPublish)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(applicationMessage.Topic))
|
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
throw new NotSupportedException("Injected application messages must contain a topic. Topic alias is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, applicationMessage).ConfigureAwait(false);
|
return _clientSessionsManager.DispatchApplicationMessage(
|
||||||
|
injectedApplicationMessage.SenderClientId,
|
||||||
|
_sessionItems,
|
||||||
|
injectedApplicationMessage.ApplicationMessage,
|
||||||
|
cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync()
|
public async Task StartAsync()
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ namespace MQTTnet.Server
|
|||||||
|
|
||||||
public string ReasonString { get; set; }
|
public string ReasonString { get; set; }
|
||||||
|
|
||||||
public List<MqttUserProperty> UserProperties { get; }
|
public List<MqttUserProperty> UserProperties { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user