Compare commits

..
Author SHA1 Message Date
Christian de647f650c Add unit test to reproduce issue 2022-11-07 19:51:44 +01:00
115 changed files with 1469 additions and 3166 deletions
+5 -9
View File
@@ -1,9 +1,5 @@
* [Core] Fixed a memory leak in _AsyncSignal_ implementation (#1586, thanks to @mario-zelger). * [Core] Fixed several dead locks by implementing a new async signal (#1552)
* [Core] Fixed an issue with bounds handling in _MqttBufferReader_ (#1593). * [Client] All waiting tasks will now fail when the client connection gets closed. This fixes several freezes (#1561)
* [Core] Performance improvements and reduced memory usage due to improved buffer copies (#1599, thanks to @xljiulang). * [Server] Fixed duplicated invocation of the event _ClientAcknowledgedPublishPacketAsync_ for QoS level 2 (#1550)
* [Server] Fix not properly reset statistics (#1587, thanks to @damikun). * [Server] Fixed issue in upgrading and downgrading of QoS levels for subscriptions and retained messages (#1560)
* [Server] Now using an empty string as the sender client ID for injected application messages (#1583, thanks to @xljiulang). * [Server] Fixed memory leak when old sessions are discarded (#1553)
* [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>
@@ -43,7 +43,7 @@ namespace MQTTnet.Extensions.ManagedClient
Task StartAsync(ManagedMqttClientOptions options); Task StartAsync(ManagedMqttClientOptions options);
Task StopAsync(bool cleanDisconnect = true); Task StopAsync();
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters); Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
@@ -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'">
@@ -62,7 +62,6 @@ namespace MQTTnet.Extensions.ManagedClient
CancellationTokenSource _publishingCancellationToken; CancellationTokenSource _publishingCancellationToken;
ManagedMqttClientStorageManager _storageManager; ManagedMqttClientStorageManager _storageManager;
bool _isCleanDisconnect;
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger) public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
{ {
@@ -161,7 +160,7 @@ namespace MQTTnet.Extensions.ManagedClient
throw new InvalidOperationException("call StartAsync before publishing messages"); throw new InvalidOperationException("call StartAsync before publishing messages");
} }
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage); MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
ManagedMqttApplicationMessage removedMessage = null; ManagedMqttApplicationMessage removedMessage = null;
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null; ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
@@ -256,12 +255,10 @@ namespace MQTTnet.Extensions.ManagedClient
_logger.Info("Started"); _logger.Info("Started");
} }
public async Task StopAsync(bool cleanDisconnect = true) public async Task StopAsync()
{ {
ThrowIfDisposed(); ThrowIfDisposed();
_isCleanDisconnect = cleanDisconnect;
StopPublishing(); StopPublishing();
StopMaintainingConnection(); StopMaintainingConnection();
@@ -385,12 +382,9 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
try try
{ {
if (_isCleanDisconnect) using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
{ {
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout)) await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
{
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
}
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -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);
} }
} }
} }
@@ -64,7 +64,7 @@ namespace MQTTnet.Tests.Client
await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false); await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false);
} }
[TestMethod] [TestMethod]
[ExpectedException(typeof(MqttCommunicationException))] [ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Wrong_Host() public async Task Connect_To_Wrong_Host()
@@ -80,8 +80,6 @@ namespace MQTTnet.Tests.Client
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
testEnvironment.IgnoreServerLogErrors = true;
testEnvironment.ServerPort = 8364; testEnvironment.ServerPort = 8364;
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
@@ -118,8 +116,6 @@ namespace MQTTnet.Tests.Client
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
testEnvironment.IgnoreServerLogErrors = true;
var server = await testEnvironment.StartServer(); var server = await testEnvironment.StartServer();
using (var lowLevelClient = testEnvironment.CreateLowLevelClient()) using (var lowLevelClient = testEnvironment.CreateLowLevelClient())
@@ -127,7 +123,7 @@ namespace MQTTnet.Tests.Client
Assert.IsFalse(lowLevelClient.IsConnected); Assert.IsFalse(lowLevelClient.IsConnected);
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build(); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build();
await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None); await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None);
Assert.IsTrue(lowLevelClient.IsConnected); Assert.IsTrue(lowLevelClient.IsConnected);
@@ -152,7 +148,7 @@ namespace MQTTnet.Tests.Client
catch catch
{ {
} }
Assert.IsFalse(lowLevelClient.IsConnected); Assert.IsFalse(lowLevelClient.IsConnected);
} }
} }
@@ -11,7 +11,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -23,66 +22,51 @@ namespace MQTTnet.Tests.Client
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod] [TestMethod]
public async Task Receive_While_Not_Cleanly_Disconnected() public async Task Subscribe_When_Offline_But_Existing_Session()
{ {
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500)) using (var testEnvironment = CreateTestEnvironment())
{ {
await testEnvironment.StartServer(o => o.WithPersistentSessions()); var server = await testEnvironment.StartServer(o => o.WithPersistentSessions());
var senderClient = await testEnvironment.ConnectClient();
// Prepare managed client.
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
await managedClient.SubscribeAsync("#");
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
var managedClientOptions = new ManagedMqttClientOptions // The connected client will crate a new session.
var mqttClient = await testEnvironment.ConnectClient();
mqttClient.Dispose();
mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
// Subscribe before starting!
await managedClient.SubscribeAsync("a");
MqttApplicationMessage applicationMessage = null;
managedClient.ApplicationMessageReceivedAsync += e =>
{ {
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder() applicationMessage = e.ApplicationMessage;
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort) return CompletedTask.Instance;
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
.WithCleanSession(false)
.Build()
}; };
// Now the client must send the new subscription.
await managedClient.StartAsync(
new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithCleanSession(false).WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()
});
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
await LongTestDelay(); await LongTestDelay();
// Send test data. await server.InjectApplicationMessage(
await senderClient.PublishStringAsync("topic1"); new InjectedMqttApplicationMessage(
await LongTestDelay(); new MqttApplicationMessage
await LongTestDelay(); {
Topic = "a"
receivedMessages.AssertReceivedCountEquals(1); }));
// Stop the managed client but keep session at server (not clean disconnect required).
await managedClient.StopAsync(false);
await LongTestDelay(); await LongTestDelay();
// Send new messages in the meantime. Assert.IsNotNull(applicationMessage);
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
await LongTestDelay();
// Start the managed client, it should receive the new message.
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
receivedMessages.AssertReceivedCountEquals(2);
// Stop and start again, no new message should be received.
for (var i = 0; i < 3; i++)
{
await managedClient.StopAsync(false);
await LongTestDelay();
await managedClient.StartAsync(managedClientOptions);
await LongTestDelay();
}
receivedMessages.AssertReceivedCountEquals(2);
} }
} }
[TestMethod] [TestMethod]
public async Task Connect_To_Invalid_Server() public async Task Connect_To_Invalid_Server()
{ {
@@ -91,10 +75,10 @@ namespace MQTTnet.Tests.Client
testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreClientLogErrors = true;
var mqttClient = testEnvironment.CreateClient(); var mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient); var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
ConnectingFailedEventArgs connectingFailedEventArgs = null; ConnectingFailedEventArgs connectingFailedEventArgs = null;
managedClient.ConnectingFailedAsync += args => managedClient.ConnectingFailedAsync += args =>
{ {
connectingFailedEventArgs = args; connectingFailedEventArgs = args;
@@ -331,7 +315,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 +343,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 +363,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);
}
}
}
@@ -52,26 +52,12 @@ namespace MQTTnet.Tests.Internal
} }
[TestMethod] [TestMethod]
public async Task Reset_Signal() public async Task Loop_Signal()
{ {
var asyncSignal = new AsyncSignal(); var asyncSignal = new AsyncSignal();
// WaitAsync should fail because no signal is available.
for (var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
try
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)))
{
await asyncSignal.WaitAsync(timeout.Token);
}
Assert.Fail("This must fail because the signal is not yet set.");
}
catch (OperationCanceledException)
{
}
asyncSignal.Set(); asyncSignal.Set();
// WaitAsync should return directly because the signal is available. // WaitAsync should return directly because the signal is available.
@@ -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.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Internal;
using MQTTnet.Packets;
namespace MQTTnet.Tests.Internal
{
[TestClass]
public sealed class MqttPacketBusItem_Tests
{
[TestMethod]
public void Fire_Completed_Event()
{
var eventFired = false;
var item = new MqttPacketBusItem(new MqttPublishPacket());
item.Completed += (_, __) =>
{
eventFired = true;
};
item.Complete();
Assert.IsTrue(eventFired);
}
[TestMethod]
[ExpectedException(typeof(TaskCanceledException))]
public async Task Wait_Packet_Bus_Item_After_Already_Canceled()
{
var item = new MqttPacketBusItem(new MqttPublishPacket());
// Finish the item before the actual
item.Cancel();
await item.WaitAsync();
}
}
}
@@ -13,129 +13,7 @@ namespace MQTTnet.Tests.Internal
{ {
[TestClass] [TestClass]
public sealed class MqttPacketBus_Tests public sealed class MqttPacketBus_Tests
{ {
[TestMethod]
public void Alternate_Priorities()
{
var bus = new MqttPacketBus();
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
Assert.AreEqual(9, bus.TotalItemsCount);
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.AreEqual(0, bus.TotalItemsCount);
}
[TestMethod]
public void Await_Single_Packet()
{
var bus = new MqttPacketBus();
var delivered = false;
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
item3.Completed += (_, __) =>
{
delivered = true;
};
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
// The third packet has the event attached.
Assert.IsTrue(delivered);
}
[TestMethod]
public void Export_Packets_Without_Dequeue()
{
var bus = new MqttPacketBus();
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
Assert.AreEqual(3, bus.TotalItemsCount);
var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control);
Assert.AreEqual(0, exportedPackets.Count);
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health);
Assert.AreEqual(0, exportedPackets.Count);
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data);
Assert.AreEqual(3, exportedPackets.Count);
Assert.AreEqual(3, bus.TotalItemsCount);
}
[TestMethod]
public async Task Fill_From_Different_Task()
{
const int MessageCount = 500;
var delayRandom = new Random();
var bus = new MqttPacketBus();
_ = Task.Run(
() =>
{
for (var i = 0; i < MessageCount; i++)
{
bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health);
Thread.Sleep(delayRandom.Next(0, 10));
}
});
for (var i = 0; i < MessageCount; i++)
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
await bus.DequeueItemAsync(timeout.Token);
}
}
Assert.AreEqual(0, bus.TotalItemsCount);
}
[TestMethod] [TestMethod]
[ExpectedException(typeof(TaskCanceledException))] [ExpectedException(typeof(TaskCanceledException))]
public async Task Wait_With_Empty_Bus() public async Task Wait_With_Empty_Bus()
@@ -147,5 +25,127 @@ namespace MQTTnet.Tests.Internal
await bus.DequeueItemAsync(timeout.Token); await bus.DequeueItemAsync(timeout.Token);
} }
} }
[TestMethod]
public async Task Fill_From_Different_Task()
{
const int MessageCount = 500;
var delayRandom = new Random();
var bus = new MqttPacketBus();
_ = Task.Run(
() =>
{
for (int i = 0; i < MessageCount; i++)
{
bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health);
Thread.Sleep(delayRandom.Next(0, 10));
}
});
for (var i = 0; i < MessageCount; i++)
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
await bus.DequeueItemAsync(timeout.Token);
}
}
Assert.AreEqual(0, bus.TotalItemsCount);
}
[TestMethod]
public void Alternate_Priorities()
{
var bus = new MqttPacketBus();
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
Assert.AreEqual(9, bus.TotalItemsCount);
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
Assert.AreEqual(0, bus.TotalItemsCount);
}
[TestMethod]
public void Export_Packets_Without_Dequeue()
{
var bus = new MqttPacketBus();
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
Assert.AreEqual(3, bus.TotalItemsCount);
var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control);
Assert.AreEqual(0, exportedPackets.Count);
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health);
Assert.AreEqual(0, exportedPackets.Count);
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data);
Assert.AreEqual(3, exportedPackets.Count);
Assert.AreEqual(3, bus.TotalItemsCount);
}
[TestMethod]
public void Await_Single_Packet()
{
var bus = new MqttPacketBus();
var delivered = false;
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
item3.Delivered += (_, __) =>
{
delivered = true;
};
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
Assert.IsFalse(delivered);
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
// The third packet has the event attached.
Assert.IsTrue(delivered);
}
} }
} }
+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
@@ -154,7 +154,7 @@ namespace MQTTnet.Tests.MQTTv5
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce); var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce);
await client.DisconnectAsync(); await client.DisconnectAsync();
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode);
} }
} }
@@ -169,7 +169,7 @@ namespace MQTTnet.Tests.MQTTv5
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce); var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce);
await client.DisconnectAsync(); await client.DisconnectAsync();
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode); Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode);
} }
} }
+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);
} }
@@ -2,10 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
@@ -19,23 +17,7 @@ namespace MQTTnet.Tests.Mockups
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient) public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
{ {
if (mqttClient == null) mqttClient.ApplicationMessageReceivedAsync += MqttClientOnApplicationMessageReceivedAsync;
{
throw new ArgumentNullException(nameof(mqttClient));
}
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
}
public int Count
{
get
{
lock (_receivedEventArgs)
{
return _receivedEventArgs.Count;
}
}
} }
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
@@ -57,25 +39,7 @@ namespace MQTTnet.Tests.Mockups
} }
} }
public string GeneratePayloadSequence() Task MqttClientOnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{
var sequence = new StringBuilder();
lock (_receivedEventArgs)
{
foreach (var receivedEventArg in _receivedEventArgs)
{
var payload = receivedEventArg.ApplicationMessage.ConvertPayloadToString();
// An empty payload is not part of the sequence!
sequence.Append(payload);
}
}
return sequence.ToString();
}
Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
{ {
lock (_receivedEventArgs) lock (_receivedEventArgs)
{ {
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Internal; using MQTTnet.Internal;
@@ -110,7 +109,7 @@ namespace MQTTnet.Tests.Mockups
configureOptions.Invoke(optionsBuilder); configureOptions.Invoke(optionsBuilder);
var options = optionsBuilder.Build(); var options = optionsBuilder.Build();
var client = CreateClient(); var client = CreateClient();
if (timeout == TimeSpan.Zero) if (timeout == TimeSpan.Zero)
@@ -138,7 +137,7 @@ namespace MQTTnet.Tests.Mockups
options = options.WithTcpServer("127.0.0.1", ServerPort); options = options.WithTcpServer("127.0.0.1", ServerPort);
var client = CreateClient(); var client = CreateClient();
if (timeout == TimeSpan.Zero) if (timeout == TimeSpan.Zero)
{ {
await client.ConnectAsync(options.Build()).ConfigureAwait(false); await client.ConnectAsync(options.Build()).ConfigureAwait(false);
@@ -162,7 +161,7 @@ namespace MQTTnet.Tests.Mockups
} }
var client = CreateClient(); var client = CreateClient();
if (timeout == TimeSpan.Zero) if (timeout == TimeSpan.Zero)
{ {
await client.ConnectAsync(options).ConfigureAwait(false); await client.ConnectAsync(options).ConfigureAwait(false);
@@ -200,16 +199,6 @@ namespace MQTTnet.Tests.Mockups
return new TestApplicationMessageReceivedHandler(mqttClient); return new TestApplicationMessageReceivedHandler(mqttClient);
} }
public TestApplicationMessageReceivedHandler CreateApplicationMessageHandler(IManagedMqttClient managedClient)
{
if (managedClient == null)
{
throw new ArgumentNullException(nameof(managedClient));
}
return new TestApplicationMessageReceivedHandler(managedClient.InternalClient);
}
public IMqttClient CreateClient() public IMqttClient CreateClient()
{ {
lock (_clients) lock (_clients)
@@ -336,7 +325,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build(); var options = optionsBuilder.Build();
var server = CreateServer(options); var server = CreateServer(options);
await server.StartAsync(); await server.StartAsync();
// The OS has chosen the port to we have to properly expose it to the tests. // The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port; ServerPort = options.DefaultEndpointOptions.Port;
return server; return server;
@@ -355,7 +344,7 @@ namespace MQTTnet.Tests.Mockups
var options = optionsBuilder.Build(); var options = optionsBuilder.Build();
var server = CreateServer(options); var server = CreateServer(options);
await server.StartAsync(); await server.StartAsync();
// The OS has chosen the port to we have to properly expose it to the tests. // The OS has chosen the port to we have to properly expose it to the tests.
ServerPort = options.DefaultEndpointOptions.Port; ServerPort = options.DefaultEndpointOptions.Port;
return server; return server;
+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);
}
}
} }
} }
+1 -1
View File
@@ -55,7 +55,7 @@ namespace MQTTnet.Tests.Server
}); });
} }
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(120)); SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(90));
Assert.AreEqual(100000, receivedMessages); Assert.AreEqual(100000, receivedMessages);
} }
@@ -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()
{ {
@@ -1,58 +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.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Publish_Tests : BaseTestClass
{
[TestMethod]
public async Task Return_Success_When_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
[TestMethod]
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
await receiver.SubscribeAsync("A");
// AtLeastOnce is required to get an ACK packet from the server.
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
Assert.AreEqual(true, publishResult.IsSuccess);
}
}
}
}
-54
View File
@@ -15,60 +15,6 @@ namespace MQTTnet.Tests.Server
[TestClass] [TestClass]
public sealed class QoS_Tests : BaseTestClass public sealed class QoS_Tests : BaseTestClass
{ {
[TestMethod]
public async Task Preserve_Message_Order_For_Queued_Messages()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer(o => o.WithPersistentSessions());
// Create a session which will contain the messages.
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
dummyClient.Dispose();
await LongTestDelay();
await LongTestDelay();
// Now inject messages which are appended to the queue of the client.
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "2", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "1", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "4", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "3", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "6", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "5", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "8", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "7", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await server.InjectApplicationMessage("T", "9", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
// Create a new client for the existing message.
var client = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
var messages = testEnvironment.CreateApplicationMessageHandler(client);
await LongTestDelay();
var payloadSequence = messages.GeneratePayloadSequence();
Assert.AreEqual("0214365879", payloadSequence);
// Disconnect and reconnect to make sure that the server will not send the messages twice.
await client.DisconnectAsync();
await LongTestDelay();
await client.ReconnectAsync();
await LongTestDelay();
payloadSequence = messages.GeneratePayloadSequence();
Assert.AreEqual("0214365879", payloadSequence);
}
}
[TestMethod] [TestMethod]
public async Task Fire_Event_On_Client_Acknowledges_QoS_0() public async Task Fire_Event_On_Client_Acknowledges_QoS_0()
{ {
+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(5000);
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(3000);
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);
} }
} }
} }
+4 -4
View File
@@ -86,7 +86,7 @@ namespace MQTTnet.Adapter
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false); await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted) if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{ {
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken); throw new OperationCanceledException("MQTT connect cancelled.", cancellationToken);
} }
// Make sure that the exception from the connect task gets thrown. // Make sure that the exception from the connect task gets thrown.
@@ -298,7 +298,7 @@ namespace MQTTnet.Adapter
// Check two times for cancellation because the call to _ReadAsync_ might block for some time. // Check two times for cancellation because the call to _ReadAsync_ might block for some time.
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return ReadFixedHeaderResult.Canceled; return ReadFixedHeaderResult.Cancelled;
} }
int bytesRead; int bytesRead;
@@ -308,7 +308,7 @@ namespace MQTTnet.Adapter
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return ReadFixedHeaderResult.Canceled; return ReadFixedHeaderResult.Cancelled;
} }
catch (SocketException) catch (SocketException)
{ {
@@ -317,7 +317,7 @@ namespace MQTTnet.Adapter
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return ReadFixedHeaderResult.Canceled; return ReadFixedHeaderResult.Cancelled;
} }
if (bytesRead == 0) if (bytesRead == 0)
+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);
+20 -31
View File
@@ -14,6 +14,26 @@ namespace MQTTnet.Client
{ {
public static class MqttClientExtensions public static class MqttClientExtensions
{ {
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.PingAsync(cancellationToken).ConfigureAwait(false);
return true;
}
catch
{
// Ignore errors.
}
return false;
}
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null) public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
{ {
if (client == null) if (client == null)
@@ -69,17 +89,6 @@ namespace MQTTnet.Client
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken); return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
} }
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client.Options == null)
{
throw new InvalidOperationException(
"The MQTT client was not connected before. A reconnect is only permitted when the client was already connected or at least tried to.");
}
return client.ConnectAsync(client.Options, cancellationToken);
}
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data) public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
{ {
if (client == null) if (client == null)
@@ -128,26 +137,6 @@ namespace MQTTnet.Client
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken); return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
} }
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
{
if (client == null)
{
throw new ArgumentNullException(nameof(client));
}
try
{
await client.PingAsync(cancellationToken).ConfigureAwait(false);
return true;
}
catch
{
// Ignore errors.
}
return false;
}
public static Task<MqttClientUnsubscribeResult> UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default) public static Task<MqttClientUnsubscribeResult> UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default)
{ {
if (mqttClient == null) if (mqttClient == null)
@@ -10,38 +10,29 @@ namespace MQTTnet.Client
public sealed class MqttClientPublishResult public sealed class MqttClientPublishResult
{ {
/// <summary> /// <summary>
/// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or /// Gets the packet identifier which was used for this publish.
/// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the topic but overall transmit
/// to the server etc. was a success.
/// </summary>
public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers;
/// <summary>
/// Gets the packet identifier which was used for this publish.
/// </summary> /// </summary>
public ushort? PacketIdentifier { get; set; } public ushort? PacketIdentifier { get; set; }
/// <summary> /// <summary>
/// Gets or sets the reason code. /// Gets or sets the reason code.
/// Hint: MQTT 5 feature only. /// Hint: MQTT 5 feature only.
/// </summary> /// </summary>
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success; public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
/// <summary> /// <summary>
/// Gets or sets the reason string. /// Gets or sets the reason string.
/// Hint: MQTT 5 feature only. /// Hint: MQTT 5 feature only.
/// </summary> /// </summary>
public string ReasonString { get; set; } public string ReasonString { get; set; }
/// <summary> /// <summary>
/// Gets or sets the user properties. /// Gets or sets the user properties.
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
/// packet. /// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add /// The feature is very similar to the HTTP header concept.
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber. /// Hint: MQTT 5 feature only.
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary> /// </summary>
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; } public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal 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();
} }
} }
@@ -12,28 +12,26 @@ namespace MQTTnet.Formatter
{ {
public sealed class MqttPubAckPacketFactory public sealed class MqttPubAckPacketFactory
{ {
public MqttPubAckPacket Create( public MqttPubAckPacket Create(MqttPublishPacket publishPacket, InterceptingPublishEventArgs interceptingPublishEventArgs)
MqttPublishPacket publishPacket,
DispatchApplicationMessageResult dispatchApplicationMessageResult)
{ {
if (publishPacket == null) if (publishPacket == null)
{ {
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, ReasonCode = MqttPubAckReasonCode.Success
ReasonString = dispatchApplicationMessageResult.ReasonString,
UserProperties = dispatchApplicationMessageResult.UserProperties
}; };
if (interceptingPublishEventArgs != null)
{
pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
pubAckPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
pubAckPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
}
return pubAckPacket; return pubAckPacket;
} }
@@ -44,12 +42,24 @@ namespace MQTTnet.Formatter
throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs)); throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs));
} }
var pubAckPacket = Create(applicationMessageReceivedEventArgs.PublishPacket, applicationMessageReceivedEventArgs.ReasonCode);
pubAckPacket.UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties;
pubAckPacket.ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString;
return pubAckPacket;
}
static MqttPubAckPacket Create(MqttPublishPacket publishPacket, MqttApplicationMessageReceivedReasonCode applicationMessageReceivedReasonCode)
{
if (publishPacket == null)
{
throw new ArgumentNullException(nameof(publishPacket));
}
var pubAckPacket = new MqttPubAckPacket var pubAckPacket = new MqttPubAckPacket
{ {
PacketIdentifier = applicationMessageReceivedEventArgs.PublishPacket.PacketIdentifier, PacketIdentifier = publishPacket.PacketIdentifier,
ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedEventArgs.ReasonCode, ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedReasonCode
UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties,
ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString
}; };
return pubAckPacket; return pubAckPacket;
@@ -25,7 +25,7 @@ namespace MQTTnet.Formatter
return pubRecPacket; return pubRecPacket;
} }
public MqttPacket Create(MqttPublishPacket publishPacket, DispatchApplicationMessageResult dispatchApplicationMessageResult) public MqttPacket Create(MqttPublishPacket publishPacket, InterceptingPublishEventArgs interceptingPublishEventArgs)
{ {
if (publishPacket == null) if (publishPacket == null)
{ {
@@ -35,11 +35,16 @@ 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 (interceptingPublishEventArgs != null)
{
pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
pubRecPacket.ReasonString = interceptingPublishEventArgs.Response.ReasonString;
pubRecPacket.UserProperties = interceptingPublishEventArgs.Response.UserProperties;
}
return pubRecPacket; return pubRecPacket;
} }
@@ -6,9 +6,9 @@ namespace MQTTnet.Formatter
{ {
public struct ReadFixedHeaderResult public struct ReadFixedHeaderResult
{ {
public static ReadFixedHeaderResult Canceled { get; } = new ReadFixedHeaderResult public static ReadFixedHeaderResult Cancelled { get; } = new ReadFixedHeaderResult
{ {
IsCanceled = true IsCancelled = true
}; };
public static ReadFixedHeaderResult ConnectionClosed { get; } = new ReadFixedHeaderResult public static ReadFixedHeaderResult ConnectionClosed { get; } = new ReadFixedHeaderResult
@@ -16,7 +16,7 @@ namespace MQTTnet.Formatter
IsConnectionClosed = true IsConnectionClosed = true
}; };
public bool IsCanceled { get; set; } public bool IsCancelled { get; set; }
public bool IsConnectionClosed { get; set; } public bool IsConnectionClosed { get; set; }
@@ -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)
+12 -18
View File
@@ -29,7 +29,7 @@ namespace MQTTnet.Internal
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
_signal.Dispose(); _signal?.Dispose();
_isDisposed = true; _isDisposed = true;
} }
@@ -48,10 +48,10 @@ namespace MQTTnet.Internal
{ {
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
{ {
return AsyncQueueDequeueResult<TItem>.Success(item); return new AsyncQueueDequeueResult<TItem>(true, item);
} }
return AsyncQueueDequeueResult<TItem>.NonSuccess; return new AsyncQueueDequeueResult<TItem>(false, default);
} }
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken) public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
@@ -60,42 +60,36 @@ namespace MQTTnet.Internal
{ {
try try
{ {
Task task = null; Task task;
lock (_syncRoot) lock (_syncRoot)
{ {
if (_isDisposed) if (_isDisposed)
{ {
return AsyncQueueDequeueResult<TItem>.NonSuccess; return new AsyncQueueDequeueResult<TItem>(false, default);
} }
if (_queue.IsEmpty) task = _signal.WaitAsync(cancellationToken);
{
task = _signal.WaitAsync(cancellationToken);
}
} }
if (task != null) await task.ConfigureAwait(false);
{
await task.ConfigureAwait(false);
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return AsyncQueueDequeueResult<TItem>.NonSuccess; return new AsyncQueueDequeueResult<TItem>(false, default);
} }
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
{ {
return AsyncQueueDequeueResult<TItem>.Success(item); return new AsyncQueueDequeueResult<TItem>(true, item);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
return AsyncQueueDequeueResult<TItem>.NonSuccess; return new AsyncQueueDequeueResult<TItem>(false, default);
} }
} }
return AsyncQueueDequeueResult<TItem>.NonSuccess; return new AsyncQueueDequeueResult<TItem>(false, default);
} }
} }
} }
@@ -4,10 +4,8 @@
namespace MQTTnet.Internal namespace MQTTnet.Internal
{ {
public sealed class AsyncQueueDequeueResult<TItem> public class AsyncQueueDequeueResult<TItem>
{ {
public static readonly AsyncQueueDequeueResult<TItem> NonSuccess = new AsyncQueueDequeueResult<TItem>(false, default);
public AsyncQueueDequeueResult(bool isSuccess, TItem item) public AsyncQueueDequeueResult(bool isSuccess, TItem item)
{ {
IsSuccess = isSuccess; IsSuccess = isSuccess;
@@ -17,10 +15,5 @@ namespace MQTTnet.Internal
public bool IsSuccess { get; } public bool IsSuccess { get; }
public TItem Item { get; } public TItem Item { get; }
public static AsyncQueueDequeueResult<TItem> Success(TItem item)
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
} }
} }
+3 -45
View File
@@ -33,18 +33,9 @@ namespace MQTTnet.Internal
{ {
_isSignaled = true; _isSignaled = true;
Cleanup();
// If there is already a waiting task let it run. // If there is already a waiting task let it run.
if (_waiter != null) _waiter?.Approve();
{ _waiter = null;
_waiter.Approve();
_waiter.Dispose();
_waiter = null;
// Since we already got a waiter the signal must be reset right now!
_isSignaled = false;
}
} }
} }
@@ -56,11 +47,8 @@ namespace MQTTnet.Internal
{ {
ThrowIfDisposed(); ThrowIfDisposed();
Cleanup();
if (_isSignaled) if (_isSignaled)
{ {
_isSignaled = false;
return CompletedTask.Instance; return CompletedTask.Instance;
} }
@@ -70,7 +58,7 @@ namespace MQTTnet.Internal
{ {
throw new InvalidOperationException("Only one waiting task is permitted per async signal."); throw new InvalidOperationException("Only one waiting task is permitted per async signal.");
} }
_waiter.Dispose(); _waiter.Dispose();
} }
@@ -79,15 +67,6 @@ namespace MQTTnet.Internal
} }
} }
void Cleanup()
{
// Cleanup if the previous waiter was cancelled.
if (_waiter != null && _waiter.Task.IsCanceled)
{
_waiter = null;
}
}
void ThrowIfDisposed() void ThrowIfDisposed()
{ {
if (_isDisposed) if (_isDisposed)
@@ -103,8 +82,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 +94,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
}
}
}
+25 -21
View File
@@ -10,36 +10,40 @@ namespace MQTTnet.Internal
{ {
public sealed class MqttPacketBusItem public sealed class MqttPacketBusItem
{ {
readonly AsyncTaskCompletionSource<bool> _promise = new AsyncTaskCompletionSource<bool>(); readonly AsyncTaskCompletionSource<int> _promise = new AsyncTaskCompletionSource<int>();
public MqttPacketBusItem(MqttPacket packet) public MqttPacketBusItem(MqttPacket packet)
{ {
Packet = packet ?? throw new ArgumentNullException(nameof(packet)); Packet = packet ?? throw new ArgumentNullException(nameof(packet));
} }
public event EventHandler Completed;
public MqttPacket Packet { get; } public MqttPacket Packet { get; }
public void Cancel() public event EventHandler Delivered;
{
_promise.TrySetCanceled();
}
public void Complete() public Task WaitForDeliveryAsync()
{
_promise.TrySetResult(true);
Completed?.Invoke(this, EventArgs.Empty);
}
public void Fail(Exception exception)
{
_promise.TrySetException(exception);
}
public Task WaitAsync()
{ {
return _promise.Task; return _promise.Task;
} }
public void MarkAsDelivered()
{
if (_promise.TrySetResult(0))
{
Delivered?.Invoke(this, EventArgs.Empty);
}
}
public void MarkAsFailed(Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
_promise.TrySetException(exception);
}
public void MarkAsCancelled()
{
_promise.TrySetCanceled();
}
} }
} }
+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'">

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