Compare commits

..
100 changed files with 1210 additions and 2515 deletions
+1 -9
View File
@@ -1,9 +1 @@
* [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).
+6 -2
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.1.4.${{github.run_number}}" VERSION: "4.1.3.${{github.run_number}}"
jobs: jobs:
build: build:
@@ -60,4 +60,8 @@ 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
+1 -7
View File
@@ -240,14 +240,8 @@ 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> <s:Boolean x:Key="/Default/UserDictionary/Words/=unsub/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=UNSUBACK/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+1 -1
View File
@@ -4,7 +4,7 @@
</p> </p>
[![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet) [![NuGet Badge](https://buildstats.info/nuget/MQTTnet)](https://www.nuget.org/packages/MQTTnet)
[![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml) [![CI](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml/badge.svg)](https://github.com/dotnet/MQTTnet/actions/workflows/ci.yml)
[![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet) [![MyGet](https://img.shields.io/myget/mqttnet/v/mqttnet?color=orange&label=MyGet-Preview)](https://www.myget.org/feed/mqttnet/package/nuget/MQTTnet)
![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg) ![Size](https://img.shields.io/github/repo-size/dotnet/MQTTnet.svg)
[![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/MQTTnet/community](https://badges.gitter.im/MQTTnet/community.svg)](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+2 -2
View File
@@ -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 implementation from WebSocket4Net is used. It provides more * Instead of the .NET implementation of WebSockets the implementaion 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
}); });
} }
} }
} }
+3 -1
View File
@@ -9,10 +9,12 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild> <EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</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,6 +12,8 @@
<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.Helpers; using MQTTnet.Tests.Extensions;
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, Array.Empty<byte>())); await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None)); await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
@@ -8,15 +8,17 @@
<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,8 +32,7 @@
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -2,59 +2,38 @@
// 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.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using System;
using System.IO.Pipelines;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
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));
_input = connection.Transport.Input; if (Connection.Transport != null)
_output = connection.Transport.Output;
}
public long BytesReceived { get; private set; }
public long BytesSent { get; private set; }
public X509Certificate2 ClientCertificate
{
get
{ {
// mqtt over tcp _input = Connection.Transport.Input;
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>(); _output = Connection.Transport.Output;
if (tlsFeature != null)
{
return tlsFeature.ClientCertificate;
}
// mqtt over websocket
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
return httpFeature?.HttpContext?.Connection.ClientCertificate;
} }
} }
@@ -62,59 +41,47 @@ namespace MQTTnet.AspNetCore
{ {
get get
{ {
// mqtt over tcp #if NETCOREAPP3_1
#if NETCOREAPP3_1_OR_GREATER if (Connection?.RemoteEndPoint != null)
if (_connection.RemoteEndPoint != null)
{ {
return _connection.RemoteEndPoint.ToString(); return Connection.RemoteEndPoint.ToString();
} }
#endif #endif
// mqtt over websocket var connection = Http?.HttpContext?.Connection;
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>(); if (connection == null)
if (httpFeature?.RemoteIpAddress != null)
{ {
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString(); return Connection.ConnectionId;
} }
return null; return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
} }
} }
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; }
public bool IsSecureConnection IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
{
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)
@@ -125,19 +92,16 @@ 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;
@@ -177,15 +141,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 exception) catch (Exception e)
{ {
// 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(exception); _input?.Complete(e);
_output?.Complete(exception); _output?.Complete(e);
throw; throw;
} }
finally finally
@@ -205,35 +169,25 @@ 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))
{ {
try var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory();
var output = _output;
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
if (result.IsCompleted)
{ {
var buffer = PacketFormatterAdapter.Encode(packet); BytesSent += msg.Length;
WritePacketBuffer(_output, buffer);
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
BytesSent += buffer.Length;
}
finally
{
PacketFormatterAdapter.Cleanup();
} }
PacketFormatterAdapter.Cleanup();
} }
} }
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer) public void Dispose()
{ {
// 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);
} }
} }
} }
+23 -50
View File
@@ -2,23 +2,21 @@
// 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)
{ {
@@ -35,22 +33,22 @@ namespace MQTTnet.AspNetCore
return false; return false;
} }
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength)) var fixedHeader = copy.First.Span[0];
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 bodySegment = GetArraySegment(ref bodySlice); var buffer = bodySlice.GetMemory().ToArray();
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength); var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown) if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
{ {
formatter.DetectProtocolVersion(receivedMqttPacket); formatter.DetectProtocolVersion(receivedMqttPacket);
@@ -63,41 +61,18 @@ namespace MQTTnet.AspNetCore
return true; return true;
} }
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input) static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
{ {
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment)) if (input.IsSingleSegment)
{ {
return segment; return input.First;
} }
// Should be rare // Should be rare
var array = input.ToArray(); return 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;
@@ -106,35 +81,33 @@ 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 == span.Length) if (index == temp.Length)
{ {
return false; return false;
} }
encodedByte = span[index]; encodedByte = temp[index];
index++; index++;
value += (byte)(encodedByte & 127) * multiplier; value += (byte)(encodedByte & 127) * multiplier;
if (multiplier > 128 * 128 * 128) if (multiplier > 128 * 128 * 128)
{ {
ThrowProtocolViolationException(span, index); throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
} }
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,7 +1,3 @@
// 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,25 +2,54 @@
// 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 : BaseBenchmark public sealed class ChannelAdapterBenchmark
{ {
MqttChannelAdapter _channelAdapter; MqttChannelAdapter _channelAdapter;
int _iterations; int _iterations;
MqttPublishPacket _packet;
MemoryStream _stream; MemoryStream _stream;
MqttPublishPacket _packet;
[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()
@@ -48,34 +77,6 @@ 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();
@@ -87,4 +88,4 @@ namespace MQTTnet.Benchmarks
return buffer.ToArray(); return buffer.ToArray();
} }
} }
} }
@@ -0,0 +1,22 @@
// 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);
}
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
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
}
}
}
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
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));
}
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter] [RPlotExporter]
[MemoryDiagnoser] [MemoryDiagnoser]
public class LoggerBenchmark : BaseBenchmark public class LoggerBenchmark
{ {
MqttNetNullLogger _nullLogger; MqttNetNullLogger _nullLogger;
MqttNetSourceLogger _sourceNullLogger; MqttNetSourceLogger _sourceNullLogger;
@@ -1,16 +1,14 @@
<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>net6.0</TargetFrameworks> <TargetFrameworks>net5.0;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>
@@ -28,6 +26,9 @@
<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>
@@ -1,40 +0,0 @@
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,8 +1,5 @@
// 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;
@@ -11,10 +8,14 @@ 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 : BaseBenchmark public class MessageDeliveryBenchmark
{ {
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 : BaseBenchmark public class MessageProcessingBenchmark
{ {
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 : BaseBenchmark public class MessageProcessingMqttConnectionContextBenchmark
{ {
IWebHost _host; IWebHost _host;
IMqttClient _mqttClient; IMqttClient _mqttClient;
@@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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,10 +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 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;
@@ -12,7 +9,7 @@ namespace MQTTnet.Benchmarks
{ {
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[MemoryDiagnoser] [MemoryDiagnoser]
public class MqttPacketReaderWriterBenchmark : BaseBenchmark public class MqttPacketReaderWriterBenchmark
{ {
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 class MqttTcpChannelBenchmark : BaseBenchmark public sealed class MqttTcpChannelBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttChannel _serverChannel; IMqttChannel _serverChannel;
+66 -121
View File
@@ -3,140 +3,85 @@
// 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
{ {
static int _selectedBenchmarkIndex; public static void Main(string[] args)
static List<Type> _benchmarks;
public static void Main(string[] arguments)
{ {
_benchmarks = CollectBenchmarks(); Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
HandleArguments(arguments); 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");
while (true) var pressedKey = Console.ReadKey(true);
switch (pressedKey.KeyChar)
{ {
RenderMenu(); case '1':
BenchmarkRunner.Run<MessageProcessingBenchmark>();
var key = Console.ReadKey(true); break;
case '2':
if (key.Key == ConsoleKey.DownArrow) BenchmarkRunner.Run<SerializerBenchmark>();
{ break;
_selectedBenchmarkIndex++; case '3':
if (_selectedBenchmarkIndex > _benchmarks.Count - 1) BenchmarkRunner.Run<LoggerBenchmark>();
{ break;
_selectedBenchmarkIndex = 0; case '4':
} BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
} break;
else if (key.Key == ConsoleKey.UpArrow) case '5':
{ BenchmarkRunner.Run<ChannelAdapterBenchmark>();
_selectedBenchmarkIndex--; break;
if (_selectedBenchmarkIndex < 0) case '6':
{ BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
_selectedBenchmarkIndex = _benchmarks.Count - 1; break;
} case '7':
} BenchmarkRunner.Run<TcpPipesBenchmark>();
else if (key.Key == ConsoleKey.Escape) break;
{ case '8':
Environment.Exit(0); BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
} break;
else if (key.Key == ConsoleKey.Enter) case '9':
{ 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();
}
} }
} }
@@ -1,240 +0,0 @@
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 : BaseBenchmark public class RoundtripProcessingBenchmark
{ {
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
@@ -1,7 +1,3 @@
// 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;
@@ -12,7 +8,7 @@ namespace MQTTnet.Benchmarks
[SimpleJob(RuntimeMoniker.Net60)] [SimpleJob(RuntimeMoniker.Net60)]
[RPlotExporter, RankColumn] [RPlotExporter, RankColumn]
[MemoryDiagnoser] [MemoryDiagnoser]
public class ServerProcessingBenchmark : BaseBenchmark public class ServerProcessingBenchmark
{ {
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
@@ -1,7 +1,3 @@
// 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;
@@ -12,7 +8,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
[MemoryDiagnoser] [MemoryDiagnoser]
public class SubscribeBenchmark : BaseBenchmark public class SubscribeBenchmark
{ {
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 : BaseBenchmark public class TcpPipesBenchmark
{ {
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 : BaseBenchmark public class TopicFilterComparerBenchmark
{ {
static readonly char[] TopicLevelSeparator = { '/' }; static readonly char[] TopicLevelSeparator = { '/' };
+15 -18
View File
@@ -1,30 +1,31 @@
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 numPublishers, int numTopicsPerPublisher,
int numTopicsPerPublisher, out Dictionary<string, List<string>> topicsByPublisher,
out Dictionary<string, List<string>> topicsByPublisher, out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher, out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
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;
} }
var numLevel1Topics = topicsPerLevel; int numLevel1Topics = topicsPerLevel;
var numLevel2Topics = topicsPerLevel; int numLevel2Topics = topicsPerLevel;
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics); var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
if (maxNumLevel3Topics <= 0) if (maxNumLevel3Topics <= 0)
@@ -34,7 +35,7 @@ namespace MQTTnet.Benchmarks
for (var p = 0; p < numPublishers; ++p) for (var p = 0; p < numPublishers; ++p)
{ {
var publisherTopicCount = 0; int publisherTopicCount = 0;
var publisherName = "pub" + p; var publisherName = "pub" + p;
for (var l1 = 0; l1 < numLevel1Topics; ++l1) for (var l1 = 0; l1 < numLevel1Topics; ++l1)
{ {
@@ -43,22 +44,19 @@ 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 = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}"; var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
AddPublisherTopic(publisherName, topic, topicsByPublisher); AddPublisherTopic(publisherName, topic, topicsByPublisher);
if (l2 == 0) if (l2 == 0)
{ {
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}"; var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher); AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
} }
if ((l1 == 0) && (l3 == 0))
if (l1 == 0 && l3 == 0)
{ {
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+"; var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher); AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
} }
@@ -77,8 +75,7 @@ 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,7 +1,3 @@
// 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;
@@ -11,7 +7,7 @@ using System.Linq;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
[MemoryDiagnoser] [MemoryDiagnoser]
public class UnsubscribeBenchmark : BaseBenchmark public class UnsubscribeBenchmark
{ {
MqttServer _mqttServer; MqttServer _mqttServer;
IMqttClient _mqttClient; IMqttClient _mqttClient;
+54
View File
@@ -0,0 +1,54 @@
<?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;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -33,8 +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;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -11,12 +11,10 @@ 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( return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
mqttClient, {
new MqttRpcClientOptions TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
{ });
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
});
} }
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions) public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
+41 -33
View File
@@ -2,24 +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 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));
@@ -28,18 +27,6 @@ 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))
@@ -62,13 +49,15 @@ 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) if (methodName == null) throw new ArgumentNullException(nameof(methodName));
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
{ {
throw new ArgumentNullException(nameof(methodName)); MethodName = methodName,
} QualityOfServiceLevel = qualityOfServiceLevel,
MqttClient = _mqttClient,
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel); Options = _options
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context); });
var requestTopic = topicNames.RequestTopic; var requestTopic = topicNames.RequestTopic;
var responseTopic = topicNames.ResponseTopic; var responseTopic = topicNames.ResponseTopic;
@@ -83,7 +72,8 @@ namespace MQTTnet.Extensions.Rpc
throw new MqttProtocolViolationException("RPC response topic is empty."); throw new MqttProtocolViolationException("RPC response topic is empty.");
} }
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic) var requestMessage = new MqttApplicationMessageBuilder()
.WithTopic(requestTopic)
.WithPayload(payload) .WithPayload(payload)
.WithQualityOfServiceLevel(qualityOfServiceLevel) .WithQualityOfServiceLevel(qualityOfServiceLevel)
.WithResponseTopic(responseTopic) .WithResponseTopic(responseTopic)
@@ -91,23 +81,25 @@ namespace MQTTnet.Extensions.Rpc
try try
{ {
var awaitable = new AsyncTaskCompletionSource<byte[]>(); #if NET452
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().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build(); var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
.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( using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
() =>
{
awaitable.TrySetCanceled();
}))
{ {
return await awaitable.Task.ConfigureAwait(false); return await awaitable.Task.ConfigureAwait(false);
} }
@@ -115,8 +107,8 @@ namespace MQTTnet.Extensions.Rpc
finally finally
{ {
_waitingCalls.TryRemove(responseTopic, out _); _waitingCalls.TryRemove(responseTopic, out _);
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false); await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
} }
} }
@@ -127,12 +119,28 @@ 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,6 +10,13 @@ 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
@@ -17,12 +24,5 @@ namespace MQTTnet.Extensions.Rpc
TopicGenerationStrategy = _topicGenerationStrategy TopicGenerationStrategy = _topicGenerationStrategy
}; };
} }
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
{
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
return this;
}
} }
} }
@@ -10,11 +10,6 @@ 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,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -10,29 +9,20 @@ namespace MQTTnet.Extensions.Rpc
{ {
public sealed class TopicGenerationContext public sealed class TopicGenerationContext
{ {
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel) public string MethodName { get; set; }
{
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 /// 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.
/// that defines the guarantee of delivery for a specific message. /// There are 3 QoS levels in MQTT:
/// There are 3 QoS levels in MQTT: /// - At most once (0): Message gets delivered no time, once or multiple times.
/// - At most once (0): Message gets delivered no time, once or multiple times. /// - At least once (1): Message gets delivered at least once (one time or more often).
/// - 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).
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary> /// </summary>
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; } public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public IMqttClient MqttClient { get; set; }
public MqttRpcClientOptions Options { get; set; }
} }
} }
@@ -33,8 +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;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
@@ -10,12 +10,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory) public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
{ {
if (mqttFactory == null) if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
{
throw new ArgumentNullException(nameof(mqttFactory));
}
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory()); return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
} }
} }
} }
@@ -5,7 +5,6 @@
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;
@@ -18,14 +17,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 MqttClientOptions _clientOptions;
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>(); readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
readonly MqttClientOptions _clientOptions;
readonly MqttClientWebSocketOptions _webSocketOptions; readonly MqttClientWebSocketOptions _webSocketOptions;
WebSocket _webSocket; WebSocket _webSocket;
@@ -36,13 +35,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 Task ConnectAsync(CancellationToken cancellationToken) public X509Certificate2 ClientCertificate { get; }
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))
@@ -56,13 +55,11 @@ namespace MQTTnet.Extensions.WebSocket4Net
uri = "wss://" + uri; uri = "wss://" + uri;
} }
} }
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
#if NET48 || NETCOREAPP3_0_OR_GREATER #if NET48 || NETCOREAPP3_1 || NET5 || NET6
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;
@@ -93,7 +90,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)
{ {
@@ -110,13 +107,15 @@ 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
} }
}; };
return ConnectInternalAsync(cancellationToken); await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
} }
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
@@ -125,23 +124,10 @@ 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;
@@ -175,55 +161,75 @@ 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 promise = new AsyncTaskCompletionSource<bool>(); var taskCompletionSource = new TaskCompletionSource<Exception>();
void ErrorHandler(object sender, ErrorEventArgs e) void ErrorHandler(object sender, ErrorEventArgs e)
{ {
promise.TrySetException(e.Exception); taskCompletionSource.TrySetResult(e.Exception);
} }
void OpenedHandler(object sender, EventArgs e) void SuccessHandler(object sender, EventArgs e)
{ {
promise.TrySetResult(true); taskCompletionSource.TrySetResult(null);
}
void ClosedHandler(object sender, EventArgs e)
{
promise.TrySetException(new MqttCommunicationException("Connection closed."));
} }
try try
{ {
_webSocket.Opened += OpenedHandler; _webSocket.Opened += SuccessHandler;
_webSocket.Closed += ClosedHandler;
_webSocket.Error += ErrorHandler; _webSocket.Error += ErrorHandler;
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout)) #pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken)) _webSocket.Open();
#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(() => promise.TrySetCanceled())) using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
{ {
try try
{ {
// OpenAsync does basically the same but without support for a cancellation token. await taskCompletionSource.Task.ConfigureAwait(false);
// So we do it on our own!
_webSocket.Open();
await promise.Task.ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested; var timeoutReached = timeoutCts.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);
@@ -245,23 +251,9 @@ namespace MQTTnet.Extensions.WebSocket4Net
} }
finally finally
{ {
_webSocket.Opened -= OpenedHandler; _webSocket.Opened -= SuccessHandler;
_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 System;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using System;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
namespace MQTTnet.Extensions.WebSocket4Net namespace MQTTnet.Extensions.WebSocket4Net
{ {
@@ -15,10 +15,7 @@ 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) if (options == null) throw new ArgumentNullException(nameof(options));
{
throw new ArgumentNullException(nameof(options));
}
switch (options.ChannelOptions) switch (options.ChannelOptions)
{ {
@@ -26,7 +23,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
{ {
return new MqttChannelAdapter( return new MqttChannelAdapter(
new MqttTcpChannel(options), new MqttTcpChannel(options),
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
packetInspector, packetInspector,
logger); logger);
} }
@@ -35,16 +32,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(options.WriterBufferSize, options.WriterBufferSizeMax)), new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
packetInspector, packetInspector,
logger); logger);
} }
default: default:
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
} }
} }
} }
} }
@@ -9,11 +9,12 @@
<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.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+72 -105
View File
@@ -2,12 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
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;
@@ -18,6 +17,10 @@ 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
@@ -28,6 +31,7 @@ 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
{ {
@@ -38,37 +42,33 @@ namespace MQTTnet.TestApp
}; };
// mqtt.eclipseprojects.io // mqtt.eclipseprojects.io
await ExecuteTestAsync( await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
"mqtt.eclipseprojects.io TCP", new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
"mqtt.eclipseprojects.io WS", new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .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( await ExecuteTestAsync("test.mosquitto.org TCP",
"test.mosquitto.org TCP", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
"test.mosquitto.org TCP - Authenticated", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithCredentials("rw", "readwrite")
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
"test.mosquitto.org TCP TLS12", new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); .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,40 +76,27 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
"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) .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
.WithTls(unsafeTls12)
.Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org WS",
"test.mosquitto.org WS", new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("test.mosquitto.org WS TLS12",
"test.mosquitto.org WS (WebSocket4Net)", new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(), .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).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( await ExecuteTestAsync("broker.emqx.io TCP",
"broker.emqx.io TCP", new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io TCP TLS12",
"broker.emqx.io TCP TLS12", new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build()); .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",
@@ -117,37 +104,23 @@ namespace MQTTnet.TestApp
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
#endif #endif
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io WS",
"broker.emqx.io WS", new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.emqx.io WS TLS12",
"broker.emqx.io WS (WebSocket4Net)", new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(), .WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).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( await ExecuteTestAsync("broker.hivemq.com TCP",
"broker.hivemq.com TCP", new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build()); .WithProtocolVersion(MqttProtocolVersion.V311).Build());
await ExecuteTestAsync( await ExecuteTestAsync("broker.hivemq.com WS",
"broker.hivemq.com WS", new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build()); .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
@@ -157,43 +130,36 @@ namespace MQTTnet.TestApp
Console.ReadLine(); Console.ReadLine();
} }
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false) static async Task ExecuteTestAsync(string name, MqttClientOptions options)
{ {
try try
{ {
Write("Testing '" + name + "'... ", ConsoleColor.Gray); Write("Testing '" + name + "'... ", ConsoleColor.Gray);
var factory = new MqttFactory(); var factory = new MqttFactory();
if (useWebSocket4Net) //factory.UseWebSocket4Net();
var client = factory.CreateMqttClient();
var topic = Guid.NewGuid().ToString();
MqttApplicationMessage receivedMessage = null;
client.ApplicationMessageReceivedAsync += e =>
{ {
factory.UseWebSocket4Net(); receivedMessage = e.ApplicationMessage;
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.");
} }
using (var client = factory.CreateMqttClient()) await client.UnsubscribeAsync(topic);
{ 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);
} }
@@ -208,5 +174,6 @@ namespace MQTTnet.TestApp
Console.ForegroundColor = color; Console.ForegroundColor = color;
Console.Write(message); Console.Write(message);
} }
} }
} }
@@ -47,12 +47,10 @@ 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);
@@ -331,7 +329,13 @@ 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.PublishStringAsync("topic", "A", retain: true); await sendingClient.PublishAsync(
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();
@@ -353,15 +357,18 @@ namespace MQTTnet.Tests.Client
await managedClient.SubscribeAsync("topic"); await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).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(0.5)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).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,
@@ -370,6 +377,7 @@ 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);
@@ -5,7 +5,7 @@
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Helpers namespace MQTTnet.Tests.Extensions
{ {
public static class MqttPacketWriterExtensions public static class MqttPacketWriterExtensions
{ {
@@ -1,51 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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();
}
}
}
@@ -1,310 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using 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.Helpers; using MQTTnet.Tests.Extensions;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Formatter namespace MQTTnet.Tests.Formatter
@@ -1,23 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using 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,28 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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);
}
}
}
+3 -2
View File
@@ -7,20 +7,21 @@
<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>
+2 -2
View File
@@ -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 packet first so the will message must be sent. c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
await LongTestDelay(); await Task.Delay(1000);
Assert.AreEqual(1, receivedMessagesCount); Assert.AreEqual(1, receivedMessagesCount);
} }
@@ -27,17 +27,6 @@ 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
+6 -27
View File
@@ -8,48 +8,27 @@ using MQTTnet.Formatter;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public sealed class Protocol_Tests public 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);
} }
} }
} }
} }
@@ -6,93 +6,68 @@ 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.Formatter;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups; using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Extensions namespace MQTTnet.Tests
{ {
[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_0_MQTT_V5()
{
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
}
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_1() public Task Execute_Success_With_QoS_1()
{ {
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311); 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]
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_2() public Task Execute_Success_With_QoS_2()
{ {
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311); return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
} }
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5()
{
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] [TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5() public Task Execute_Success_With_QoS_2_MQTT_V5()
{ {
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500); return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
} }
[TestMethod]
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod]
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
{
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestMethod] [TestMethod]
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic() public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
{ {
@@ -115,31 +90,6 @@ namespace MQTTnet.Tests.Extensions
} }
} }
[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()
@@ -148,24 +98,14 @@ namespace MQTTnet.Tests.Extensions
{ {
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())
@@ -211,6 +151,57 @@ namespace MQTTnet.Tests.Extensions
} }
} }
[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)
@@ -223,4 +214,4 @@ namespace MQTTnet.Tests.Extensions
} }
} }
} }
} }
@@ -1,7 +1,6 @@
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
@@ -61,28 +60,5 @@ 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,104 +114,6 @@ 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()
{ {
-3
View File
@@ -26,9 +26,6 @@ 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);
+7 -3
View File
@@ -121,9 +121,10 @@ 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)
@@ -149,6 +150,8 @@ 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();
@@ -159,12 +162,13 @@ 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 LongTestDelay(); await Task.Delay(TimeSpan.FromSeconds(5));
Assert.AreEqual(true, hasReceive); Assert.AreEqual(true, hasReceive);
} }
} }
#endif
[DataTestMethod] [DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)] [DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
@@ -13,56 +13,12 @@ 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()
{ {
+32 -42
View File
@@ -1,3 +1,4 @@
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;
@@ -9,51 +10,33 @@ 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);
await LongTestDelay().ConfigureAwait(false); var c1 = await testEnvironment.ConnectClient();
Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count); c1.ApplicationMessageReceivedAsync += e =>
{
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);
} }
} }
@@ -64,17 +47,24 @@ namespace MQTTnet.Tests.Server
{ {
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false); var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); var receivedMessagesCount = 0;
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 takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false); var c2 = await testEnvironment.ConnectClient(clientOptions);
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
await LongTestDelay().ConfigureAwait(false); await Task.Delay(1000);
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count); Assert.AreEqual(1, receivedMessagesCount);
} }
} }
} }
+13 -11
View File
@@ -19,6 +19,7 @@ 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();
@@ -31,6 +32,7 @@ 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;
@@ -205,7 +207,7 @@ namespace MQTTnet.Client
if (clientWasConnected) if (clientWasConnected)
{ {
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options); var disconnectPacket = _packetFactories.Disconnect.Create(options);
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false); await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
} }
} }
@@ -239,7 +241,7 @@ namespace MQTTnet.Client
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage); var publishPacket = _packetFactories.Publish.Create(applicationMessage);
switch (applicationMessage.QualityOfServiceLevel) switch (applicationMessage.QualityOfServiceLevel)
{ {
@@ -299,7 +301,7 @@ namespace MQTTnet.Client
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
var subscribePacket = MqttPacketFactories.Subscribe.Create(options); var subscribePacket = _packetFactories.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);
@@ -317,7 +319,7 @@ namespace MQTTnet.Client
ThrowIfDisposed(); ThrowIfDisposed();
ThrowIfNotConnected(); ThrowIfNotConnected();
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options); var unsubscribePacket = _packetFactories.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);
@@ -345,7 +347,7 @@ namespace MQTTnet.Client
{ {
if (!eventArgs.ProcessingFailed) if (!eventArgs.ProcessingFailed)
{ {
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs); var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
return SendAsync(pubAckPacket, cancellationToken); return SendAsync(pubAckPacket, cancellationToken);
} }
} }
@@ -353,7 +355,7 @@ namespace MQTTnet.Client
{ {
if (!eventArgs.ProcessingFailed) if (!eventArgs.ProcessingFailed)
{ {
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs); var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
return SendAsync(pubRecPacket, cancellationToken); return SendAsync(pubRecPacket, cancellationToken);
} }
} }
@@ -371,7 +373,7 @@ namespace MQTTnet.Client
try try
{ {
var connectPacket = MqttPacketFactories.Connect.Create(options); var connectPacket = _packetFactories.Connect.Create(options);
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false); var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
@@ -533,7 +535,7 @@ namespace MQTTnet.Client
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket) async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
{ {
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); var applicationMessage = _applicationMessageFactory.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);
@@ -599,7 +601,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 = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound); var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
return SendAsync(pubRelPacket, cancellationToken); return SendAsync(pubRelPacket, cancellationToken);
} }
@@ -608,7 +610,7 @@ namespace MQTTnet.Client
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{ {
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken); return SendAsync(pubCompPacket, cancellationToken);
} }
@@ -635,7 +637,7 @@ namespace MQTTnet.Client
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false); var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false); var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
@@ -10,12 +10,20 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientCertificateValidationEventArgs : EventArgs public sealed class MqttClientCertificateValidationEventArgs : EventArgs
{ {
public X509Certificate Certificate { get; set; } public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions)
{
Certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
Chain = chain ?? throw new ArgumentNullException(nameof(chain));
SslPolicyErrors = sslPolicyErrors;
ClientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
}
public X509Chain Chain { get; set; } public X509Certificate Certificate { get; }
public SslPolicyErrors SslPolicyErrors { get; set; } public X509Chain Chain { get; }
public IMqttClientChannelOptions ClientOptions { get; set; } public IMqttClientChannelOptions ClientOptions { get; }
public SslPolicyErrors SslPolicyErrors { get; }
} }
} }
@@ -12,11 +12,18 @@ namespace MQTTnet.Client
{ {
public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs) public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs)
{ {
if (eventArgs.SslPolicyErrors == SslPolicyErrors.None) var sslPolicyErrors = eventArgs.SslPolicyErrors;
if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
{
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
}
if (sslPolicyErrors == SslPolicyErrors.None)
{ {
return true; return true;
} }
if (eventArgs.Chain.ChainStatus.Any(c => if (eventArgs.Chain.ChainStatus.Any(c =>
c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation)) c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
{ {
@@ -13,8 +13,8 @@ namespace MQTTnet.Client
public sealed class MqttClientOptionsBuilderTlsParameters public sealed class MqttClientOptionsBuilderTlsParameters
{ {
public bool UseTls { get; set; } public bool UseTls { get; set; }
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } = MqttClientDefaultCertificateValidationHandler.Handle;
#if NET48 || NETCOREAPP3_1 || NET5 || NET6 #if NET48 || NETCOREAPP3_1 || NET5 || NET6
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13; public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
@@ -11,7 +11,7 @@ namespace MQTTnet.Client
{ {
public sealed class MqttClientTlsOptions public sealed class MqttClientTlsOptions
{ {
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } = MqttClientDefaultCertificateValidationHandler.Handle;
public bool UseTls { get; set; } public bool UseTls { get; set; }
@@ -6,100 +6,17 @@ 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.IsEnabled) if (logger == null) throw new ArgumentNullException(nameof(logger));
{
return;
}
logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1 }, exception); return new MqttNetSourceLogger(logger, source);
}
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)
@@ -109,7 +26,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)
@@ -119,14 +36,10 @@ 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>( public static void Verbose<TParameter1, TParameter2, TParameter3>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2,
this MqttNetSourceLogger logger,
string message,
TParameter1 parameter1,
TParameter2 parameter2,
TParameter3 parameter3) TParameter3 parameter3)
{ {
if (!logger.IsEnabled) if (!logger.IsEnabled)
@@ -134,7 +47,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)
@@ -147,74 +60,134 @@ 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 MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source) public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
{ {
if (logger == null) if (!logger.IsEnabled)
{ {
throw new ArgumentNullException(nameof(logger)); return;
} }
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);
}
return new MqttNetSourceLogger(logger, source); 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);
} }
} }
} }
@@ -7,9 +7,9 @@ using MQTTnet.Packets;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public static class MqttApplicationMessageFactory public sealed class MqttApplicationMessageFactory
{ {
public static MqttApplicationMessage Create(MqttPublishPacket publishPacket) public MqttApplicationMessage Create(MqttPublishPacket publishPacket)
{ {
if (publishPacket == null) if (publishPacket == null)
{ {
+30 -58
View File
@@ -7,24 +7,21 @@ 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 _maxPosition; int _initialPosition;
int _offset; int _length;
int _position; int _position;
public int BytesLeft => _maxPosition - _position; public bool EndOfStream => _position == _length;
public bool EndOfStream => BytesLeft == 0; public int Position => _position;
public int Position => _position - _offset; public int BytesLeft => _length - _position;
public byte[] ReadBinaryData() public byte[] ReadBinaryData()
{ {
@@ -38,7 +35,7 @@ namespace MQTTnet.Formatter
ValidateReceiveBuffer(length); ValidateReceiveBuffer(length);
var result = new byte[length]; var result = new byte[length];
MqttMemoryHelper.Copy(_buffer, _position, result, 0, length); Array.Copy(_buffer, _position, result, 0, length);
_position += length; _position += length;
return result; return result;
@@ -47,6 +44,7 @@ namespace MQTTnet.Formatter
public byte ReadByte() public byte ReadByte()
{ {
ValidateReceiveBuffer(1); ValidateReceiveBuffer(1);
return _buffer[_position++]; return _buffer[_position++];
} }
@@ -54,32 +52,25 @@ namespace MQTTnet.Formatter
{ {
ValidateReceiveBuffer(4); ValidateReceiveBuffer(4);
#if NETCOREAPP3_0_OR_GREATER var byte0 = _buffer[_position++];
var value = BinaryPrimitives.ReadUInt32BigEndian(_buffer.AsSpan(_position)); var byte1 = _buffer[_position++];
#else var byte2 = _buffer[_position++];
var byte0 = _buffer[_position]; var byte3 = _buffer[_position++];
var byte1 = _buffer[_position + 1];
var byte2 = _buffer[_position + 2];
var byte3 = _buffer[_position + 3];
var value = (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); return (uint)((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
#endif
_position += 4;
return value;
} }
public byte[] ReadRemainingData() public byte[] ReadRemainingData()
{ {
var bufferLength = BytesLeft; var bufferLength = _length - _position;
if (bufferLength == 0) if (bufferLength == 0)
{ {
return EmptyBuffer.Array; return EmptyBuffer.Array;
} }
var buffer = new byte[bufferLength]; var buffer = new byte[bufferLength];
MqttMemoryHelper.Copy(_buffer, _position, buffer, 0, bufferLength); Array.Copy(_buffer, _position, buffer, 0, bufferLength);
_position += bufferLength;
return buffer; return buffer;
} }
@@ -92,16 +83,10 @@ 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;
} }
@@ -110,17 +95,10 @@ namespace MQTTnet.Formatter
{ {
ValidateReceiveBuffer(2); ValidateReceiveBuffer(2);
#if NETCOREAPP3_0_OR_GREATER var msb = _buffer[_position++];
var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(_position)); var lsb = _buffer[_position++];
#else
var msb = _buffer[_position];
var lsb = _buffer[_position + 1];
var value = (ushort)((msb << 8) | lsb); return (ushort)((msb << 8) | lsb);
#endif
_position += 2;
return value;
} }
public uint ReadVariableByteInteger() public uint ReadVariableByteInteger()
@@ -147,29 +125,23 @@ namespace MQTTnet.Formatter
public void Seek(int position) public void Seek(int position)
{ {
_position = _offset + position; _position = _initialPosition + position;
} }
public void SetBuffer(ArraySegment<byte> buffer) public void SetBuffer(byte[] buffer, int position, int length)
{ {
SetBuffer(buffer.Array, buffer.Offset, buffer.Count); _buffer = buffer;
} _initialPosition = position;
_position = position;
public void SetBuffer(byte[] buffer, int offset, int length) _length = 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)
{ {
var newPosition = _position + length; if (_length < _position + length)
if (_maxPosition < newPosition)
{ {
throw new MqttProtocolViolationException($"Expected at least {newPosition} bytes but there are only {_maxPosition} bytes"); throw new MqttProtocolViolationException($"Expected at least {Position + length} bytes but there are only {_length} bytes");
} }
} }
} }
+18 -26
View File
@@ -6,7 +6,6 @@ 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
@@ -64,30 +63,17 @@ namespace MQTTnet.Formatter
return _buffer; return _buffer;
} }
public static int GetVariableByteIntegerSize(uint value) public static int GetLengthOfVariableInteger(uint value)
{ {
// From RFC: Table 2.4 Size of Remaining Length field var result = 0;
var x = value;
// 0 (0x00) to 127 (0x7F) do
if (value <= 127)
{ {
return 1; x /= 128;
} result++;
} while (x > 0);
// 128 (0x80, 0x01) to 16 383 (0xFF, 0x7F) return result;
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)
@@ -104,12 +90,18 @@ 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));
} }
WriteBinary(propertyWriter._buffer, 0, propertyWriter.Length); throw new InvalidOperationException($"{nameof(propertyWriter)} must be of type {nameof(MqttBufferWriter)}");
} }
public void WriteBinary(byte[] buffer, int offset, int count) public void WriteBinary(byte[] buffer, int offset, int count)
@@ -126,7 +118,7 @@ namespace MQTTnet.Formatter
EnsureAdditionalCapacity(count); EnsureAdditionalCapacity(count);
MqttMemoryHelper.Copy(buffer, offset, _buffer, _position, count); Array.Copy(buffer, offset, _buffer, _position, count);
IncreasePosition(count); IncreasePosition(count);
} }
@@ -150,7 +142,7 @@ namespace MQTTnet.Formatter
_buffer[_position] = (byte)(valueLength >> 8); _buffer[_position] = (byte)(valueLength >> 8);
_buffer[_position + 1] = (byte)valueLength; _buffer[_position + 1] = (byte)valueLength;
MqttMemoryHelper.Copy(value, 0, _buffer, _position + 2, valueLength); Array.Copy(value, 0, _buffer, _position + 2, valueLength);
IncreasePosition(valueLength + 2); IncreasePosition(valueLength + 2);
} }
} }
@@ -188,7 +180,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);
} }
} }
+6 -6
View File
@@ -37,28 +37,28 @@ namespace MQTTnet.Formatter
public byte[] ToArray() public byte[] ToArray()
{ {
if (Payload.Count == 0) if (Packet.Count == 0)
{ {
return Packet.ToArray(); return Packet.ToArray();
} }
var buffer = new byte[Length]; var buffer = new byte[Length];
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count); Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return buffer; return buffer;
} }
public ArraySegment<byte> Join() public ArraySegment<byte> Join()
{ {
if (Payload.Count == 0) if (Packet.Count == 0)
{ {
return Packet; return Packet;
} }
var buffer = new byte[Length]; var buffer = new byte[Length];
MqttMemoryHelper.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count); Array.Copy(Packet.Array, Packet.Offset, buffer, 0, Packet.Count);
MqttMemoryHelper.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count); Array.Copy(Payload.Array, Payload.Offset, buffer, Packet.Count, Payload.Count);
return new ArraySegment<byte>(buffer); return new ArraySegment<byte>(buffer);
} }
+13 -13
View File
@@ -4,30 +4,30 @@
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public static class MqttPacketFactories public sealed class MqttPacketFactories
{ {
public static MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory(); public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
public static MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory(); public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
public static MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory(); public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
public static MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory(); public MqttPubAckPacketFactory PubAck { get; } = new MqttPubAckPacketFactory();
public static MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory(); public MqttPubCompPacketFactory PubComp { get; } = new MqttPubCompPacketFactory();
public static MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory(); public MqttPublishPacketFactory Publish { get; } = new MqttPublishPacketFactory();
public static MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory(); public MqttPubRecPacketFactory PubRec { get; } = new MqttPubRecPacketFactory();
public static MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory(); public MqttPubRelPacketFactory PubRel { get; } = new MqttPubRelPacketFactory();
public static MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory(); public MqttSubAckPacketFactory SubAck { get; } = new MqttSubAckPacketFactory();
public static MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory(); public MqttSubscribePacketFactory Subscribe { get; } = new MqttSubscribePacketFactory();
public static MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory(); public MqttUnsubAckPacketFactory UnsubAck { get; } = new MqttUnsubAckPacketFactory();
public static MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory(); public MqttUnsubscribePacketFactory Unsubscribe { get; } = new MqttUnsubscribePacketFactory();
} }
} }
@@ -14,6 +14,7 @@ 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)
@@ -21,19 +22,28 @@ 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,7 +25,10 @@ namespace MQTTnet.Formatter
return pubRecPacket; return pubRecPacket;
} }
public MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult) public MqttPacket Create(
MqttPublishPacket publishPacket,
InterceptingPublishEventArgs interceptingPublishEventArgs,
DispatchApplicationMessageResult dispatchApplicationMessageResult)
{ {
if (publishPacket == null) if (publishPacket == null)
{ {
@@ -35,11 +38,21 @@ namespace MQTTnet.Formatter
var pubRecPacket = new MqttPubRecPacket var pubRecPacket = new MqttPubRecPacket
{ {
PacketIdentifier = publishPacket.PacketIdentifier, PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = (MqttPubRecReasonCode)dispatchApplicationMessageResult.ReasonCode, ReasonCode = MqttPubRecReasonCode.Success
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.Unsubscribe: case MqttControlPacketType.Unsubscibe:
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.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(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.Unsubscribe, 0x02); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 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.Unsubscribe: case MqttControlPacketType.Unsubscibe:
return DecodeUnsubscribePacket(receivedMqttPacket.Body); return DecodeUnsubscribePacket(receivedMqttPacket.Body);
case MqttControlPacketType.UnsubAck: case MqttControlPacketType.UnsubAck:
return DecodeUnsubAckPacket(receivedMqttPacket.Body); return DecodeUnsubAckPacket(receivedMqttPacket.Body);
@@ -12,8 +12,6 @@ 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));
@@ -42,9 +40,9 @@ namespace MQTTnet.Formatter.V5
remainingLength += (uint)publishPacket.Payload.Length; remainingLength += (uint)publishPacket.Payload.Length;
} }
var remainingLengthSize = MqttBufferWriter.GetVariableByteIntegerSize(remainingLength); var remainingLengthSize = MqttBufferWriter.GetLengthOfVariableInteger(remainingLength);
var headerSize = FixedHeaderSize + remainingLengthSize; var headerSize = 1 + 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)
@@ -524,7 +522,7 @@ namespace MQTTnet.Formatter.V5
_bufferWriter.WriteString(topicFilter); _bufferWriter.WriteString(topicFilter);
} }
return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscribe, 0x02); return MqttBufferWriter.BuildFixedHeader(MqttControlPacketType.Unsubscibe, 0x02);
} }
static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet) static void ThrowIfPacketIdentifierIsInvalid(ushort packetIdentifier, MqttPacket packet)
@@ -264,27 +264,15 @@ namespace MQTTnet.Implementations
} }
} }
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) bool InternalUserCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{ {
var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler; var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler;
if (certificateValidationHandler != null) if (certificateValidationHandler != null)
{ {
var eventArgs = new MqttClientCertificateValidationEventArgs var eventArgs = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _tcpOptions);
{
Certificate = x509Certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _tcpOptions
};
return certificateValidationHandler(eventArgs); return certificateValidationHandler(eventArgs);
} }
if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
{
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
}
return sslPolicyErrors == SslPolicyErrors.None; return sslPolicyErrors == SslPolicyErrors.None;
} }
@@ -224,14 +224,7 @@ namespace MQTTnet.Implementations
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{ {
// TODO: Find a way to add client options to same callback. Problem is that they have a different type. // TODO: Find a way to add client options to same callback. Problem is that they have a different type.
var context = new MqttClientCertificateValidationEventArgs var context = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _options);
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors,
ClientOptions = _options
};
return certificateValidationHandler(context); return certificateValidationHandler(context);
}; };
#endif #endif
+1 -23
View File
@@ -34,12 +34,11 @@ 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!
@@ -103,8 +102,6 @@ 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)
@@ -117,37 +114,18 @@ 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();
} }
} }
@@ -1,37 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.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;
}
}
}
}
@@ -1,29 +0,0 @@
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
}
}
}
+1 -2
View File
@@ -41,8 +41,7 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
<NoWarn>1591;NETSDK1138</NoWarn> <NoWarn>1591</NoWarn>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='uap10.0'">
@@ -1,102 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
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,
Unsubscribe = 10, Unsubscibe = 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; } = string.Empty; public string SenderClientId { get; set; }
public MqttApplicationMessage ApplicationMessage { get; set; } public MqttApplicationMessage ApplicationMessage { get; set; }
} }
@@ -2,27 +2,15 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class DispatchApplicationMessageResult public sealed class DispatchApplicationMessageResult
{ {
public DispatchApplicationMessageResult(int reasonCode, bool closeConnection, string reasonString, List<MqttUserProperty> userProperties) public DispatchApplicationMessageResult(int matchingSubscribersCount)
{ {
ReasonCode = reasonCode; MatchingSubscribersCount = matchingSubscribersCount;
CloseConnection = closeConnection;
ReasonString = reasonString;
UserProperties = userProperties;
} }
public bool CloseConnection { get; } public int MatchingSubscribersCount { get; }
public int ReasonCode { get; }
public string ReasonString { get; }
public List<MqttUserProperty> UserProperties { get; }
} }
} }
+110 -91
View File
@@ -12,21 +12,27 @@ 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 : IDisposable public sealed class MqttClient
{ {
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 = new CancellationTokenSource(); CancellationTokenSource _cancellationToken;
bool _disconnectPacketSent; bool _disconnectPacketSent;
public MqttClient( public MqttClient(
@@ -39,13 +45,14 @@ namespace MQTTnet.Server
IMqttNetLogger logger) IMqttNetLogger logger)
{ {
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_eventContainer = eventContainer ?? throw new ArgumentNullException(nameof(eventContainer)); _eventContainer = 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)
{ {
@@ -73,56 +80,53 @@ 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;
try using (_cancellationToken = new CancellationTokenSource())
{ {
var cancellationToken = _cancellationToken.Token; var cancellationToken = _cancellationToken.Token;
_ = Task.Factory.StartNew(() => SendPacketsLoop(cancellationToken), cancellationToken, TaskCreationOptions.PreferFairness, TaskScheduler.Default) try
.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;
_cancellationToken?.TryCancel(); _packetDispatcher.CancelAll();
_cancellationToken?.Dispose();
_cancellationToken = null;
}
if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent) if (!IsTakenOver && !IsCleanDisconnect && Session.LatestConnectPacket.WillFlag && !Session.WillMessageSent)
{ {
var willPublishPacket = MqttPacketFactories.Publish.Create(Session.LatestConnectPacket); var willPublishPacket = _packetFactories.Publish.Create(Session.LatestConnectPacket);
var willApplicationMessage = MqttApplicationMessageFactory.Create(willPublishPacket); var willApplicationMessage = _applicationMessageFactory.Create(willPublishPacket);
_ = _sessionsManager.DispatchApplicationMessage(Id, Session.Items, willApplicationMessage, CancellationToken.None); _ = _sessionsManager.DispatchApplicationMessage(Id, willApplicationMessage);
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)
@@ -176,12 +180,6 @@ 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);
@@ -210,17 +208,41 @@ namespace MQTTnet.Server
{ {
HandleTopicAlias(publishPacket); HandleTopicAlias(publishPacket);
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket); InterceptingPublishEventArgs interceptingPublishEventArgs = null;
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
var closeConnection = false;
var processPublish = true;
var dispatchApplicationMessageResult = if (_eventContainer.InterceptingPublishEvent.HasHandlers)
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;
}
if (dispatchApplicationMessageResult.CloseConnection) await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false);
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:
@@ -230,19 +252,19 @@ namespace MQTTnet.Server
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
var pubAckPacket = MqttPacketFactories.PubAck.Create(publishPacket, dispatchApplicationMessageResult); var pubAckPacket = _packetFactories.PubAck.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(pubAckPacket));
break; break;
} }
case MqttQualityOfServiceLevel.ExactlyOnce: case MqttQualityOfServiceLevel.ExactlyOnce:
{ {
var pubRecPacket = MqttPacketFactories.PubRec.Create(publishPacket, dispatchApplicationMessageResult); var pubRecPacket = _packetFactories.PubRec.Create(publishPacket, interceptingPublishEventArgs, dispatchResult);
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.");
} }
} }
} }
@@ -251,7 +273,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 = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
return CompletedTask.Instance; return CompletedTask.Instance;
@@ -259,7 +281,7 @@ namespace MQTTnet.Server
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket) void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
{ {
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(pubCompPacket));
} }
@@ -267,7 +289,7 @@ namespace MQTTnet.Server
{ {
var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false); var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
var subAckPacket = MqttPacketFactories.SubAck.Create(subscribePacket, subscribeResult); var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(subAckPacket));
@@ -281,7 +303,7 @@ namespace MQTTnet.Server
{ {
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{ {
var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch); var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
} }
} }
@@ -291,7 +313,7 @@ namespace MQTTnet.Server
{ {
var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false); var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
var unsubAckPacket = MqttPacketFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult); var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(unsubAckPacket));
@@ -322,7 +344,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);
} }
} }
} }
@@ -348,7 +370,6 @@ 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
@@ -358,12 +379,12 @@ namespace MQTTnet.Server
{ {
await Task.Yield(); await Task.Yield();
currentPacket = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (currentPacket == null) if (packet == 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)
{ {
@@ -376,69 +397,73 @@ 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, currentPacket, Session.Items); var interceptingPacketEventArgs = new InterceptingPacketEventArgs(cancellationToken, Id, Endpoint, packet, Session.Items);
await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false); await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);
currentPacket = interceptingPacketEventArgs.Packet; packet = interceptingPacketEventArgs.Packet;
processPacket = interceptingPacketEventArgs.ProcessPacket; processPacket = interceptingPacketEventArgs.ProcessPacket;
} }
if (!processPacket || currentPacket == null) if (!processPacket || packet == 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(currentPacket); Statistics.HandleReceivedPacket(packet);
if (currentPacket is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
} }
else if (currentPacket is MqttPubAckPacket pubAckPacket) else if (packet is MqttPubAckPacket pubAckPacket)
{ {
await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false); await HandleIncomingPubAckPacket(pubAckPacket).ConfigureAwait(false);
} }
else if (currentPacket is MqttPubCompPacket pubCompPacket) else if (packet is MqttPubCompPacket pubCompPacket)
{ {
await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false); await HandleIncomingPubCompPacket(pubCompPacket).ConfigureAwait(false);
} }
else if (currentPacket is MqttPubRecPacket pubRecPacket) else if (packet is MqttPubRecPacket pubRecPacket)
{ {
await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false); await HandleIncomingPubRecPacket(pubRecPacket).ConfigureAwait(false);
} }
else if (currentPacket is MqttPubRelPacket pubRelPacket) else if (packet is MqttPubRelPacket pubRelPacket)
{ {
HandleIncomingPubRelPacket(pubRelPacket); HandleIncomingPubRelPacket(pubRelPacket);
} }
else if (currentPacket is MqttSubscribePacket subscribePacket) else if (packet is MqttSubscribePacket subscribePacket)
{ {
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
} }
else if (currentPacket is MqttUnsubscribePacket unsubscribePacket) else if (packet is MqttUnsubscribePacket unsubscribePacket)
{ {
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
} }
else if (currentPacket is MqttPingReqPacket) else if (packet is MqttPingReqPacket)
{ {
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));
} }
else if (currentPacket is MqttPingRespPacket) else if (packet 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 (currentPacket is MqttDisconnectPacket) else if (packet is MqttDisconnectPacket)
{ {
IsCleanDisconnect = true; IsCleanDisconnect = true;
return; return;
} }
else else
{ {
throw new MqttProtocolViolationException("Packet not allowed"); if (!_packetDispatcher.TryDispatch(packet))
{
throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
}
} }
} }
} }
@@ -449,25 +474,11 @@ namespace MQTTnet.Server
{ {
if (exception is MqttCommunicationException) if (exception is MqttCommunicationException)
{ {
_logger.Warning(exception, "Client '{0}': Communication exception while receiving packets", Id); _logger.Warning(exception, "Client '{0}': Communication exception while receiving client 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.Publish(logLevel, exception, "Client '{0}': Error while processing {1} packet", Id, currentPacket.GetRfcName()); _logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id);
} }
} }
} }
@@ -519,15 +530,15 @@ namespace MQTTnet.Server
{ {
if (exception is MqttCommunicationTimedOutException) if (exception is MqttCommunicationTimedOutException)
{ {
_logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to timeout", Id); _logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", Id);
} }
else if (exception is MqttCommunicationException) else if (exception is MqttCommunicationException)
{ {
_logger.Warning(exception, "Client '{0}': Sending PUBLISH packet failed due to communication exception", Id); _logger.Warning(exception, "Client '{0}': Sending publish packet failed: 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)
@@ -545,7 +556,15 @@ namespace MQTTnet.Server
void StopInternal() void StopInternal()
{ {
_cancellationToken?.TryCancel(); try
{
_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)
@@ -554,8 +573,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 = MqttPacketFactories.Disconnect.Create(reasonCode); var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout)) using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{ {
@@ -564,7 +583,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,6 +28,7 @@ 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;
@@ -126,118 +127,81 @@ namespace MQTTnet.Server
_logger.Verbose("Session for client '{0}' deleted.", clientId); _logger.Verbose("Session for client '{0}' deleted.", clientId);
} }
public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage( public async Task<DispatchApplicationMessageResult> DispatchApplicationMessage(string senderId, MqttApplicationMessage applicationMessage)
string senderId,
IDictionary senderSessionItems,
MqttApplicationMessage applicationMessage,
CancellationToken cancellationToken)
{ {
var processPublish = true; var matchingSubscribersCount = 0;
var closeConnection = false; try
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)
{ {
var interceptingPublishEventArgs = new InterceptingPublishEventArgs(applicationMessage, cancellationToken, senderId, senderSessionItems); if (applicationMessage.Retain)
if (string.IsNullOrEmpty(interceptingPublishEventArgs.ApplicationMessage.Topic))
{ {
// This can happen if a topic alias us used but the topic is await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false);
// unknown to the server. }
interceptingPublishEventArgs.Response.ReasonCode = MqttPubAckReasonCode.TopicNameInvalid;
interceptingPublishEventArgs.ProcessPublish = false; List<MqttSession> subscriberSessions;
lock (_sessionsManagementLock)
{
subscriberSessions = _subscriberSessions.ToList();
} }
await _eventContainer.InterceptingPublishEvent.InvokeAsync(interceptingPublishEventArgs).ConfigureAwait(false); // Calculate application message topic hash once for subscription checks
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _);
applicationMessage = interceptingPublishEventArgs.ApplicationMessage; foreach (var session in subscriberSessions)
closeConnection = interceptingPublishEventArgs.CloseConnection;
processPublish = interceptingPublishEventArgs.ProcessPublish;
reasonString = interceptingPublishEventArgs.Response.ReasonString;
userProperties = interceptingPublishEventArgs.Response.UserProperties;
reasonCode = (int)interceptingPublishEventArgs.Response.ReasonCode;
}
// Process the application message...
if (processPublish && applicationMessage != null)
{
var matchingSubscribersCount = 0;
try
{ {
if (applicationMessage.Retain) if(!session.TryCheckSubscriptions(
applicationMessage.Topic,
topicHash,
applicationMessage.QualityOfServiceLevel,
senderId,
out var checkSubscriptionsResult))
{ {
await _retainedMessagesManager.UpdateMessage(senderId, applicationMessage).ConfigureAwait(false); // Checking the subscriptions has failed for the session. The session
// will be ignored.
continue;
} }
List<MqttSession> subscriberSessions; if (!checkSubscriptionsResult.IsSubscribed)
lock (_sessionsManagementLock)
{ {
subscriberSessions = _subscriberSessions.ToList(); continue;
} }
// Calculate application message topic hash once for subscription checks var newPublishPacket = _packetFactories.Publish.Create(applicationMessage);
MqttSubscription.CalculateTopicHash(applicationMessage.Topic, out var topicHash, out _, out _); newPublishPacket.QualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel;
newPublishPacket.SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers;
foreach (var session in subscriberSessions) if (newPublishPacket.QualityOfServiceLevel > 0)
{ {
if (!session.TryCheckSubscriptions( newPublishPacket.PacketIdentifier = session.PacketIdentifierProvider.GetNextPacketIdentifier();
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) if (checkSubscriptionsResult.RetainAsPublished)
{ {
reasonCode = (int)MqttPubAckReasonCode.NoMatchingSubscribers; // Transfer the original retain state from the publisher. This is a MQTTv5 feature.
await FireApplicationMessageNotConsumedEvent(applicationMessage, senderId).ConfigureAwait(false); 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);
} }
catch (Exception exception)
if (matchingSubscribersCount == 0)
{ {
_logger.Error(exception, "Unhandled exception while processing next queued application message."); 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); return new DispatchApplicationMessageResult(matchingSubscribersCount);
} }
public void Dispose() public void Dispose()
@@ -324,7 +288,7 @@ namespace MQTTnet.Server
} }
var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false); var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
var connAckPacket = MqttPacketFactories.ConnAck.Create(validatingConnectionEventArgs); var connAckPacket = _packetFactories.ConnAck.Create(validatingConnectionEventArgs);
if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success) if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success)
{ {
@@ -466,7 +430,7 @@ namespace MQTTnet.Server
{ {
foreach (var retainedMessageMatch in subscribeResult.RetainedMessages) foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{ {
var publishPacket = MqttPacketFactories.Publish.Create(retainedMessageMatch); var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
} }
} }
@@ -532,11 +496,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))
{ {
@@ -555,7 +519,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;
@@ -564,7 +528,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)
@@ -573,7 +537,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;
@@ -585,15 +549,19 @@ 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(oldClient.Id, MqttClientDisconnectType.Takeover, oldClient.Endpoint, oldClient.Session.Items); var eventArgs = new ClientDisconnectedEventArgs(
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; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
/// <summary> /// <summary>
/// Timestamp of the last package that has been sent to the client ("received" from the client's perspective) /// Timestamp of the last package that has been sent to the client ("received" from the client's perspective)
/// </summary> /// </summary>
public DateTime LastPacketReceivedTimestamp { get; private set; } public DateTime LastPacketReceivedTimestamp { get; private set; }
/// <summary> /// <summary>
/// Timestamp of the last package that has been received from the client ("sent" from the client's perspective) /// Timestamp of the last package that has been received from the client ("sent" from the client's perspective)
/// </summary> /// </summary>
public DateTime LastPacketSentTimestamp { get; private set; } public DateTime LastPacketSentTimestamp { get; private set; }
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount); public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; private set; }
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount); public long SentApplicationMessagesCount => Interlocked.Read(ref _sentApplicationMessagesCount);
public long ReceivedApplicationMessagesCount => Interlocked.Read(ref _receivedApplicationMessagesCount);
public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount); public long SentPacketsCount => Interlocked.Read(ref _sentPacketsCount);
public long ReceivedPacketsCount => Interlocked.Read(ref _receivedPacketsCount);
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,13 +91,5 @@ 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,21 +55,18 @@ namespace MQTTnet.Server
{ {
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions)) if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
{ {
possibleSubscriptions.AddRange(noWildcardSubscriptions); possibleSubscriptions.AddRange(noWildcardSubscriptions.ToList());
} }
foreach (var wcs in _wildcardSubscriptionsByTopicHash) foreach (var wcs in _wildcardSubscriptionsByTopicHash)
{ {
var wildcardSubscriptions = wcs.Value;
var subscriptionHash = wcs.Key; var subscriptionHash = wcs.Key;
var subscriptionsByHashMask = wcs.Value.SubscriptionsByHashMask; var subscriptionHashMask = wildcardSubscriptions.HashMask;
foreach (var shm in subscriptionsByHashMask)
if ((topicHash & subscriptionHashMask) == subscriptionHash)
{ {
var subscriptionHashMask = shm.Key; possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList());
if ((topicHash & subscriptionHashMask) == subscriptionHash)
{
var subscriptions = shm.Value;
possibleSubscriptions.AddRange(subscriptions);
}
} }
} }
} }
@@ -268,8 +265,8 @@ namespace MQTTnet.Server
{ {
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{ {
subscriptions.RemoveSubscription(existingSubscription); subscriptions.Subscriptions.Remove(existingSubscription);
if (subscriptions.SubscriptionsByHashMask.Count == 0) if (subscriptions.Subscriptions.Count == 0)
{ {
_wildcardSubscriptionsByTopicHash.Remove(topicHash); _wildcardSubscriptionsByTopicHash.Remove(topicHash);
} }
@@ -349,9 +346,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 subscriptions)) if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subs))
{ {
subscriptions.RemoveSubscription(existingSubscription); subs.Subscriptions.Remove(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
} }
} }
@@ -373,11 +370,11 @@ namespace MQTTnet.Server
{ {
if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) if (!_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
{ {
subscriptions = new TopicHashMaskSubscriptions(); subscriptions = new TopicHashMaskSubscriptions(topicHashMask);
_wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions); _wildcardSubscriptionsByTopicHash.Add(topicHash, subscriptions);
} }
subscriptions.AddSubscription(subscription); subscriptions.Subscriptions.Add(subscription);
} }
else else
{ {
@@ -3,32 +3,17 @@ using System.Collections.Generic;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
/// <summary> /// <summary>
/// Helper class that stores subscriptions by their topic hash mask. /// Helper class that stores the topic hash mask common to all contained Subscriptions for direct access.
/// </summary> /// </summary>
public sealed class TopicHashMaskSubscriptions public sealed class TopicHashMaskSubscriptions
{ {
public Dictionary<ulong, HashSet<MqttSubscription>> SubscriptionsByHashMask { get; } = new Dictionary<ulong, HashSet<MqttSubscription>>(); public TopicHashMaskSubscriptions(ulong hashMask)
public void AddSubscription(MqttSubscription subscription)
{ {
if (!SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) HashMask = hashMask;
{
subscriptions = new HashSet<MqttSubscription>();
SubscriptionsByHashMask.Add(subscription.TopicHashMask, subscriptions);
}
subscriptions.Add(subscription);
} }
public void RemoveSubscription(MqttSubscription subscription) public ulong HashMask { get; }
{
if (SubscriptionsByHashMask.TryGetValue(subscription.TopicHashMask, out var subscriptions)) public HashSet<MqttSubscription> Subscriptions { get; } = new HashSet<MqttSubscription>();
{
subscriptions.Remove(subscription);
if (subscriptions.Count == 0)
{
SubscriptionsByHashMask.Remove(subscription.TopicHashMask);
}
}
}
} }
} }
+29 -11
View File
@@ -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 Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage, CancellationToken cancellationToken = default) public async Task InjectApplicationMessage(InjectedMqttApplicationMessage injectedApplicationMessage)
{ {
if (injectedApplicationMessage == null) if (injectedApplicationMessage == null)
{ {
@@ -223,16 +223,34 @@ namespace MQTTnet.Server
ThrowIfNotStarted(); ThrowIfNotStarted();
if (string.IsNullOrEmpty(injectedApplicationMessage.ApplicationMessage.Topic)) var processPublish = true;
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.");
} }
return _clientSessionsManager.DispatchApplicationMessage( await _clientSessionsManager.DispatchApplicationMessage(injectedApplicationMessage.SenderClientId, applicationMessage).ConfigureAwait(false);
injectedApplicationMessage.SenderClientId,
_sessionItems,
injectedApplicationMessage.ApplicationMessage,
cancellationToken);
} }
public async Task StartAsync() public async Task StartAsync()
+1 -1
View File
@@ -14,6 +14,6 @@ namespace MQTTnet.Server
public string ReasonString { get; set; } public string ReasonString { get; set; }
public List<MqttUserProperty> UserProperties { get; set; } public List<MqttUserProperty> UserProperties { get; }
} }
} }