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

|

|
||||||
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/MQTTnet/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public static class Client_Connection_Samples
|
|||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
* This sample creates a simple MQTT client and connects to a public broker using a WebSocket connection.
|
||||||
* Instead of the .NET implementation of WebSockets the implementaion from WebSocket4Net is used. It provides more
|
* Instead of the .NET implementation of WebSockets the implementation from WebSocket4Net is used. It provides more
|
||||||
* encryption algorithms and supports more platforms.
|
* encryption algorithms and supports more platforms.
|
||||||
*
|
*
|
||||||
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
* This is a modified version of the sample _Connect_Client_! See other sample for more details.
|
||||||
@@ -386,4 +386,4 @@ public static class Client_Connection_Samples
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,10 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
|
|
||||||
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
// ReSharper disable UnusedType.Global
|
||||||
|
// ReSharper disable UnusedMember.Global
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
// ReSharper disable EmptyConstructor
|
||||||
|
// ReSharper disable MemberCanBeMadeStatic.Local
|
||||||
|
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
|
namespace MQTTnet.Samples.Server;
|
||||||
|
|
||||||
|
public static class Server_Intercepting_Samples
|
||||||
|
{
|
||||||
|
public static async Task Intercept_Application_Messages()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This sample starts a simple MQTT server which manipulate all processed application messages.
|
||||||
|
* Please see _Server_Simple_Samples_ for more details on how to start a server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var mqttFactory = new MqttFactory();
|
||||||
|
var mqttServerOptions = new MqttServerOptionsBuilder().WithDefaultEndpoint().Build();
|
||||||
|
|
||||||
|
using (var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions))
|
||||||
|
{
|
||||||
|
mqttServer.InterceptingPublishAsync += args =>
|
||||||
|
{
|
||||||
|
// Here we only change the topic of the received application message.
|
||||||
|
// but also changing the payload etc. is required. Changing the QoS after
|
||||||
|
// transmitting is not supported and makes no sense at all.
|
||||||
|
args.ApplicationMessage.Topic += "/manipulated";
|
||||||
|
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await mqttServer.StartAsync();
|
||||||
|
|
||||||
|
Console.WriteLine("Press Enter to exit.");
|
||||||
|
Console.ReadLine();
|
||||||
|
await mqttServer.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,8 +12,6 @@
|
|||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||||
|
|||||||
@@ -1,26 +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.Collections.Generic;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
|
||||||
{
|
|
||||||
public class ConnectionContextMockup : ConnectionContext
|
|
||||||
{
|
|
||||||
public override string ConnectionId { get; set; }
|
|
||||||
|
|
||||||
public override IFeatureCollection Features { get; }
|
|
||||||
|
|
||||||
public override IDictionary<object, object> Items { get; set; }
|
|
||||||
public override IDuplexPipe Transport { get; set; }
|
|
||||||
|
|
||||||
public ConnectionContextMockup()
|
|
||||||
{
|
|
||||||
//Transport = new DefaultConnectionContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,20 +2,24 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Server;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||||
{
|
{
|
||||||
public class ConnectionHandlerMockup: IMqttServerAdapter
|
public sealed class ConnectionHandlerMockup : IMqttServerAdapter
|
||||||
{
|
{
|
||||||
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
|
||||||
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
public Func<IMqttChannelAdapter, Task> ClientHandler { get; set; }
|
||||||
|
public TaskCompletionSource<MqttConnectionContext> Context { get; } = new TaskCompletionSource<MqttConnectionContext>();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OnConnectedAsync(ConnectionContext connection)
|
public async Task OnConnectedAsync(ConnectionContext connection)
|
||||||
{
|
{
|
||||||
@@ -42,9 +46,5 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
|||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using System.IO.Pipelines;
|
|||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||||
{
|
{
|
||||||
public class DuplexPipeMockup : IDuplexPipe
|
public sealed class DuplexPipeMockup : IDuplexPipe
|
||||||
{
|
{
|
||||||
public DuplexPipeMockup()
|
public DuplexPipeMockup()
|
||||||
{
|
{
|
||||||
@@ -20,9 +20,8 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
|||||||
|
|
||||||
PipeWriter IDuplexPipe.Output => Send.Writer;
|
PipeWriter IDuplexPipe.Output => Send.Writer;
|
||||||
|
|
||||||
|
public Pipe Receive { get; }
|
||||||
|
|
||||||
public Pipe Receive { get; set; }
|
public Pipe Send { get; }
|
||||||
|
|
||||||
public Pipe Send { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using System.Buffers;
|
|||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||||
{
|
{
|
||||||
public class LimitedMemoryPool : MemoryPool<byte>
|
public sealed class LimitedMemoryPool : MemoryPool<byte>
|
||||||
{
|
{
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ using System.Buffers;
|
|||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests.Mockups
|
namespace MQTTnet.AspNetCore.Tests.Mockups
|
||||||
{
|
{
|
||||||
public class MemoryOwner : IMemoryOwner<byte>
|
public sealed class MemoryOwner : IMemoryOwner<byte>
|
||||||
{
|
{
|
||||||
private readonly byte[] _raw;
|
readonly byte[] _raw;
|
||||||
|
|
||||||
public MemoryOwner(int size)
|
public MemoryOwner(int size)
|
||||||
{
|
{
|
||||||
@@ -27,11 +27,11 @@ namespace MQTTnet.AspNetCore.Tests.Mockups
|
|||||||
Memory = _raw;
|
Memory = _raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Memory<byte> Memory { get; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
ArrayPool<byte>.Shared.Return(_raw);
|
ArrayPool<byte>.Shared.Return(_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Memory<byte> Memory { get; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,44 +2,29 @@
|
|||||||
// 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 Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Connections;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Connections;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.AspNetCore.Tests.Mockups;
|
using MQTTnet.AspNetCore.Tests.Mockups;
|
||||||
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Tests.Extensions;
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore.Tests
|
namespace MQTTnet.AspNetCore.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class MqttConnectionContextTest
|
public class MqttConnectionContextTest
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
|
|
||||||
{
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
var pipe = new DuplexPipeMockup();
|
|
||||||
var connection = new DefaultConnectionContext();
|
|
||||||
connection.Transport = pipe;
|
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
|
||||||
|
|
||||||
pipe.Receive.Writer.Complete();
|
|
||||||
|
|
||||||
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TestCorruptedConnectPacket()
|
public async Task TestCorruptedConnectPacket()
|
||||||
{
|
{
|
||||||
@@ -49,13 +34,43 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
var connection = new DefaultConnectionContext();
|
var connection = new DefaultConnectionContext();
|
||||||
connection.Transport = pipe;
|
connection.Transport = pipe;
|
||||||
var ctx = new MqttConnectionContext(serializer, connection);
|
var ctx = new MqttConnectionContext(serializer, connection);
|
||||||
|
|
||||||
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, new byte[0]));
|
await pipe.Receive.Writer.WriteAsync(writer.AddMqttHeader(MqttControlPacketType.Connect, Array.Empty<byte>()));
|
||||||
|
|
||||||
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
await Assert.ThrowsExceptionAsync<MqttProtocolViolationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||||
|
|
||||||
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
// the first exception should complete the pipes so if someone tries to use the connection after that it should throw immidiatly
|
||||||
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
await Assert.ThrowsExceptionAsync<InvalidOperationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task TestEndpoint()
|
||||||
|
{
|
||||||
|
var mockup = new ConnectionHandlerMockup();
|
||||||
|
|
||||||
|
|
||||||
|
using (var host = new WebHostBuilder().UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
||||||
|
.UseStartup<Startup>()
|
||||||
|
.ConfigureServices(
|
||||||
|
(hostContext, services) =>
|
||||||
|
{
|
||||||
|
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
|
||||||
|
services.AddSingleton<IMqttServerAdapter>(mockup);
|
||||||
|
})
|
||||||
|
.Build())
|
||||||
|
|
||||||
|
using (var client = new MqttFactory().CreateMqttClient())
|
||||||
|
{
|
||||||
|
host.Start();
|
||||||
|
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost").Build(), CancellationToken.None);
|
||||||
|
|
||||||
|
var ctx = await mockup.Context.Task;
|
||||||
|
#if NETCOREAPP3_1
|
||||||
|
var ep = IPEndPoint.Parse(ctx.Endpoint);
|
||||||
|
Assert.IsNotNull(ep);
|
||||||
|
#endif
|
||||||
|
Assert.IsNotNull(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
|
// COMMENTED OUT DUE TO DEAD LOCK? OR VERY VERY SLOW PERFORMANCE ON LOCAL DEV MACHINE. TEST WAS STILL RUNNING AFTER SEVERAL MINUTES!
|
||||||
@@ -78,7 +93,7 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
|
|
||||||
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
// await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task TestLargePacket()
|
public async Task TestLargePacket()
|
||||||
{
|
{
|
||||||
@@ -94,43 +109,25 @@ namespace MQTTnet.AspNetCore.Tests
|
|||||||
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
Assert.IsTrue(readResult.Buffer.Length > 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Startup
|
[TestMethod]
|
||||||
|
public async Task TestReceivePacketAsyncThrowsWhenReaderCompleted()
|
||||||
|
{
|
||||||
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
var pipe = new DuplexPipeMockup();
|
||||||
|
var connection = new DefaultConnectionContext();
|
||||||
|
connection.Transport = pipe;
|
||||||
|
var ctx = new MqttConnectionContext(serializer, connection);
|
||||||
|
|
||||||
|
pipe.Receive.Writer.Complete();
|
||||||
|
|
||||||
|
await Assert.ThrowsExceptionAsync<MqttCommunicationException>(() => ctx.ReceivePacketAsync(CancellationToken.None));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Startup
|
||||||
{
|
{
|
||||||
public void Configure(IApplicationBuilder app)
|
public void Configure(IApplicationBuilder app)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task TestEndpoint()
|
|
||||||
{
|
|
||||||
var mockup = new ConnectionHandlerMockup();
|
|
||||||
|
|
||||||
|
|
||||||
using (var host = new WebHostBuilder()
|
|
||||||
.UseKestrel(kestrel => kestrel.ListenLocalhost(1883, listener => listener.Use((ctx, next) => mockup.OnConnectedAsync(ctx))))
|
|
||||||
.UseStartup<Startup>()
|
|
||||||
.ConfigureServices((hostContext, services) =>
|
|
||||||
{
|
|
||||||
services.AddHostedMqttServer(o => o.WithoutDefaultEndpoint());
|
|
||||||
services.AddSingleton<IMqttServerAdapter>(mockup);
|
|
||||||
})
|
|
||||||
.Build())
|
|
||||||
|
|
||||||
using (var client = new MqttFactory().CreateMqttClient())
|
|
||||||
{
|
|
||||||
host.Start();
|
|
||||||
await client.ConnectAsync(new MqttClientOptionsBuilder()
|
|
||||||
.WithTcpServer("localhost")
|
|
||||||
.Build(), CancellationToken.None);
|
|
||||||
|
|
||||||
var ctx = await mockup.Context.Task;
|
|
||||||
#if NETCOREAPP3_1
|
|
||||||
var ep = IPEndPoint.Parse(ctx.Endpoint);
|
|
||||||
Assert.IsNotNull(ep);
|
|
||||||
#endif
|
|
||||||
Assert.IsNotNull(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ using MQTTnet.Packets;
|
|||||||
namespace MQTTnet.AspNetCore.Tests
|
namespace MQTTnet.AspNetCore.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class ReaderExtensionsTest
|
public sealed class ReaderExtensionsTest
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestTryDeserialize()
|
public void TestTryDeserialize()
|
||||||
|
|||||||
@@ -8,17 +8,15 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj"/>
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="wwwroot\"/>
|
<Folder Include="wwwroot\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,38 +2,59 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using Microsoft.AspNetCore.Http.Connections.Features;
|
using Microsoft.AspNetCore.Http.Connections.Features;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.AspNetCore.Client.Tcp;
|
using MQTTnet.AspNetCore.Client.Tcp;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
|
||||||
using System.IO.Pipelines;
|
|
||||||
using System.Security.Cryptography.X509Certificates;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
public sealed class MqttConnectionContext : IMqttChannelAdapter
|
||||||
{
|
{
|
||||||
|
readonly ConnectionContext _connection;
|
||||||
readonly AsyncLock _writerLock = new AsyncLock();
|
readonly AsyncLock _writerLock = new AsyncLock();
|
||||||
|
|
||||||
PipeReader _input;
|
PipeReader _input;
|
||||||
PipeWriter _output;
|
PipeWriter _output;
|
||||||
|
|
||||||
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
public MqttConnectionContext(MqttPacketFormatterAdapter packetFormatterAdapter, ConnectionContext connection)
|
||||||
{
|
{
|
||||||
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
PacketFormatterAdapter = packetFormatterAdapter ?? throw new ArgumentNullException(nameof(packetFormatterAdapter));
|
||||||
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
_connection = connection ?? throw new ArgumentNullException(nameof(connection));
|
||||||
|
|
||||||
if (Connection.Transport != null)
|
_input = connection.Transport.Input;
|
||||||
|
_output = connection.Transport.Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long BytesReceived { get; private set; }
|
||||||
|
|
||||||
|
public long BytesSent { get; private set; }
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
_input = Connection.Transport.Input;
|
// mqtt over tcp
|
||||||
_output = Connection.Transport.Output;
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return tlsFeature.ClientCertificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
return httpFeature?.HttpContext?.Connection.ClientCertificate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,47 +62,59 @@ namespace MQTTnet.AspNetCore
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if NETCOREAPP3_1
|
// mqtt over tcp
|
||||||
if (Connection?.RemoteEndPoint != null)
|
#if NETCOREAPP3_1_OR_GREATER
|
||||||
|
if (_connection.RemoteEndPoint != null)
|
||||||
{
|
{
|
||||||
return Connection.RemoteEndPoint.ToString();
|
return _connection.RemoteEndPoint.ToString();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var connection = Http?.HttpContext?.Connection;
|
// mqtt over websocket
|
||||||
if (connection == null)
|
var httpFeature = _connection.Features.Get<IHttpConnectionFeature>();
|
||||||
|
if (httpFeature?.RemoteIpAddress != null)
|
||||||
{
|
{
|
||||||
return Connection.ConnectionId;
|
return new IPEndPoint(httpFeature.RemoteIpAddress, httpFeature.RemotePort).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{connection.RemoteIpAddress}:{connection.RemotePort}";
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsSecureConnection => Http?.HttpContext?.Request?.IsHttps ?? false;
|
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate => Http?.HttpContext?.Connection?.ClientCertificate;
|
|
||||||
|
|
||||||
public ConnectionContext Connection { get; }
|
|
||||||
|
|
||||||
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
|
||||||
|
|
||||||
public long BytesSent { get; set; }
|
|
||||||
|
|
||||||
public long BytesReceived { get; set; }
|
|
||||||
|
|
||||||
public bool IsReadingPacket { get; private set; }
|
public bool IsReadingPacket { get; private set; }
|
||||||
|
|
||||||
IHttpContextFeature Http => Connection.Features.Get<IHttpContextFeature>();
|
public bool IsSecureConnection
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// mqtt over tcp
|
||||||
|
var tlsFeature = _connection.Features.Get<ITlsConnectionFeature>();
|
||||||
|
if (tlsFeature != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mqtt over websocket
|
||||||
|
var httpFeature = _connection.Features.Get<IHttpContextFeature>();
|
||||||
|
if (httpFeature?.HttpContext != null)
|
||||||
|
{
|
||||||
|
return httpFeature.HttpContext.Request.IsHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MqttPacketFormatterAdapter PacketFormatterAdapter { get; }
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
public async Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (Connection is TcpConnection tcp && !tcp.IsConnected)
|
if (_connection is TcpConnection tcp && !tcp.IsConnected)
|
||||||
{
|
{
|
||||||
await tcp.StartAsync().ConfigureAwait(false);
|
await tcp.StartAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_input = Connection.Transport.Input;
|
_input = _connection.Transport.Input;
|
||||||
_output = Connection.Transport.Output;
|
_output = _connection.Transport.Output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||||
@@ -92,16 +125,19 @@ namespace MQTTnet.AspNetCore
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_writerLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
public async Task<MqttPacket> ReceivePacketAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var input = Connection.Transport.Input;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
ReadResult readResult;
|
ReadResult readResult;
|
||||||
var readTask = input.ReadAsync(cancellationToken);
|
var readTask = _input.ReadAsync(cancellationToken);
|
||||||
if (readTask.IsCompleted)
|
if (readTask.IsCompleted)
|
||||||
{
|
{
|
||||||
readResult = readTask.Result;
|
readResult = readTask.Result;
|
||||||
@@ -141,15 +177,15 @@ namespace MQTTnet.AspNetCore
|
|||||||
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
// before yielding the read again.
|
// before yielding the read again.
|
||||||
input.AdvanceTo(consumed, observed);
|
_input.AdvanceTo(consumed, observed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
// completing the channel makes sure that there is no more data read after a protocol error
|
// completing the channel makes sure that there is no more data read after a protocol error
|
||||||
_input?.Complete(e);
|
_input?.Complete(exception);
|
||||||
_output?.Complete(e);
|
_output?.Complete(exception);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -169,25 +205,35 @@ namespace MQTTnet.AspNetCore
|
|||||||
|
|
||||||
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
public async Task SendPacketAsync(MqttPacket packet, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var formatter = PacketFormatterAdapter;
|
|
||||||
|
|
||||||
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var buffer = formatter.Encode(packet);
|
try
|
||||||
var msg = buffer.Join().AsMemory();
|
|
||||||
var output = _output;
|
|
||||||
var result = await output.WriteAsync(msg, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (result.IsCompleted)
|
|
||||||
{
|
{
|
||||||
BytesSent += msg.Length;
|
var buffer = PacketFormatterAdapter.Encode(packet);
|
||||||
}
|
|
||||||
|
|
||||||
PacketFormatterAdapter.Cleanup();
|
WritePacketBuffer(_output, buffer);
|
||||||
|
await _output.FlushAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
BytesSent += buffer.Length;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PacketFormatterAdapter.Cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
static void WritePacketBuffer(PipeWriter output, MqttPacketBuffer buffer)
|
||||||
{
|
{
|
||||||
|
// copy MqttPacketBuffer's Packet and Payload to the same buffer block of PipeWriter
|
||||||
|
// MqttPacket will be transmitted within the bounds of a WebSocket frame after PipeWriter.FlushAsync
|
||||||
|
|
||||||
|
var span = output.GetSpan(buffer.Length);
|
||||||
|
|
||||||
|
buffer.Packet.AsSpan().CopyTo(span);
|
||||||
|
buffer.Payload.AsSpan().CopyTo(span.Slice(buffer.Packet.Count));
|
||||||
|
|
||||||
|
output.Advance(buffer.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,21 +2,23 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using MQTTnet.Adapter;
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace MQTTnet.AspNetCore
|
namespace MQTTnet.AspNetCore
|
||||||
{
|
{
|
||||||
public static class ReaderExtensions
|
public static class ReaderExtensions
|
||||||
{
|
{
|
||||||
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
public static bool TryDecode(this MqttPacketFormatterAdapter formatter,
|
||||||
in ReadOnlySequence<byte> input,
|
in ReadOnlySequence<byte> input,
|
||||||
out MqttPacket packet,
|
out MqttPacket packet,
|
||||||
out SequencePosition consumed,
|
out SequencePosition consumed,
|
||||||
out SequencePosition observed,
|
out SequencePosition observed,
|
||||||
out int bytesRead)
|
out int bytesRead)
|
||||||
{
|
{
|
||||||
@@ -33,22 +35,22 @@ namespace MQTTnet.AspNetCore
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fixedHeader = copy.First.Span[0];
|
if (!TryReadBodyLength(ref copy, out var headerLength, out var bodyLength))
|
||||||
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
copy = copy.Slice(headerLength);
|
||||||
if (copy.Length < bodyLength)
|
if (copy.Length < bodyLength)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var bodySlice = copy.Slice(0, bodyLength);
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
var buffer = bodySlice.GetMemory().ToArray();
|
var bodySegment = GetArraySegment(ref bodySlice);
|
||||||
|
|
||||||
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, bodySegment, headerLength + bodyLength);
|
||||||
|
|
||||||
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
{
|
{
|
||||||
formatter.DetectProtocolVersion(receivedMqttPacket);
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
@@ -61,18 +63,41 @@ namespace MQTTnet.AspNetCore
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ReadOnlyMemory<byte> GetMemory(this in ReadOnlySequence<byte> input)
|
static ArraySegment<byte> GetArraySegment(ref ReadOnlySequence<byte> input)
|
||||||
{
|
{
|
||||||
if (input.IsSingleSegment)
|
if (input.IsSingleSegment && MemoryMarshal.TryGetArray(input.First, out var segment))
|
||||||
{
|
{
|
||||||
return input.First;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be rare
|
// Should be rare
|
||||||
return input.ToArray();
|
var array = input.ToArray();
|
||||||
|
return new ArraySegment<byte>(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||||
|
{
|
||||||
|
var valueSequence = input.Slice(0, Math.Min(5, input.Length));
|
||||||
|
if (valueSequence.IsSingleSegment)
|
||||||
|
{
|
||||||
|
var valueSpan =
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
valueSequence.FirstSpan;
|
||||||
|
#else
|
||||||
|
valueSequence.First.Span;
|
||||||
|
#endif
|
||||||
|
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<byte> valueSpan = stackalloc byte[8];
|
||||||
|
valueSequence.CopyTo(valueSpan);
|
||||||
|
valueSpan = valueSpan.Slice(0, (int)valueSequence.Length);
|
||||||
|
return TryReadBodyLength(valueSpan, out headerLength, out bodyLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadBodyLength(ReadOnlySpan<byte> span, out int headerLength, out int bodyLength)
|
||||||
{
|
{
|
||||||
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||||
var multiplier = 1;
|
var multiplier = 1;
|
||||||
@@ -81,33 +106,35 @@ namespace MQTTnet.AspNetCore
|
|||||||
var index = 1;
|
var index = 1;
|
||||||
headerLength = 0;
|
headerLength = 0;
|
||||||
bodyLength = 0;
|
bodyLength = 0;
|
||||||
|
|
||||||
var temp = input.Slice(0, Math.Min(5, input.Length)).GetMemory().Span;
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (index == temp.Length)
|
if (index == span.Length)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedByte = temp[index];
|
encodedByte = span[index];
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
value += (byte)(encodedByte & 127) * multiplier;
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
if (multiplier > 128 * 128 * 128)
|
if (multiplier > 128 * 128 * 128)
|
||||||
{
|
{
|
||||||
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
ThrowProtocolViolationException(span, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplier *= 128;
|
multiplier *= 128;
|
||||||
} while ((encodedByte & 128) != 0);
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
input = input.Slice(index);
|
|
||||||
|
|
||||||
headerLength = index;
|
headerLength = index;
|
||||||
bodyLength = value;
|
bodyLength = value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ThrowProtocolViolationException(ReadOnlySpan<byte> valueSpan, int index)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", valueSpan.Slice(1, index).ToArray())}).");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public abstract class BaseBenchmark
|
public abstract class BaseBenchmark
|
||||||
|
|||||||
@@ -2,54 +2,25 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Internal;
|
|
||||||
using MQTTnet.Packets;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class ChannelAdapterBenchmark
|
public sealed class ChannelAdapterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttChannelAdapter _channelAdapter;
|
MqttChannelAdapter _channelAdapter;
|
||||||
int _iterations;
|
int _iterations;
|
||||||
MemoryStream _stream;
|
|
||||||
MqttPublishPacket _packet;
|
MqttPublishPacket _packet;
|
||||||
|
MemoryStream _stream;
|
||||||
[GlobalSetup]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
_packet = new MqttPublishPacket
|
|
||||||
{
|
|
||||||
Topic = "A"
|
|
||||||
};
|
|
||||||
|
|
||||||
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
|
||||||
|
|
||||||
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
|
||||||
|
|
||||||
_iterations = 10000;
|
|
||||||
|
|
||||||
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
|
||||||
|
|
||||||
for (var i = 0; i < _iterations; i++)
|
|
||||||
{
|
|
||||||
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
_stream.Position = 0;
|
|
||||||
|
|
||||||
var channel = new MemoryMqttChannel(_stream);
|
|
||||||
|
|
||||||
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public void Receive_10000_Messages()
|
public void Receive_10000_Messages()
|
||||||
@@ -77,6 +48,34 @@ namespace MQTTnet.Benchmarks
|
|||||||
_stream.Position = 0;
|
_stream.Position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_packet = new MqttPublishPacket
|
||||||
|
{
|
||||||
|
Topic = "A"
|
||||||
|
};
|
||||||
|
|
||||||
|
var serializer = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
|
||||||
|
var serializedPacket = Join(serializer.Encode(_packet).Join());
|
||||||
|
|
||||||
|
_iterations = 10000;
|
||||||
|
|
||||||
|
_stream = new MemoryStream(_iterations * serializedPacket.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < _iterations; i++)
|
||||||
|
{
|
||||||
|
_stream.Write(serializedPacket, 0, serializedPacket.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
_stream.Position = 0;
|
||||||
|
|
||||||
|
var channel = new MemoryMqttChannel(_stream);
|
||||||
|
|
||||||
|
_channelAdapter = new MqttChannelAdapter(channel, serializer, null, new MqttNetEventLogger());
|
||||||
|
}
|
||||||
|
|
||||||
static byte[] Join(params ArraySegment<byte>[] chunks)
|
static byte[] Join(params ArraySegment<byte>[] chunks)
|
||||||
{
|
{
|
||||||
var buffer = new MemoryStream();
|
var buffer = new MemoryStream();
|
||||||
@@ -88,4 +87,4 @@ namespace MQTTnet.Benchmarks
|
|||||||
return buffer.ToArray();
|
return buffer.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Validators;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// this options may be used to run benchmarks in debugmode and attach a performance profiler
|
|
||||||
/// https://benchmarkdotnet.org/Configs/Configs.htm
|
|
||||||
/// </summary>
|
|
||||||
public class AllowNonOptimized : BaseConfig
|
|
||||||
{
|
|
||||||
public AllowNonOptimized()
|
|
||||||
{
|
|
||||||
AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS
|
|
||||||
AddJob(Job.InProcess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Configs;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class BaseConfig : ManualConfig
|
|
||||||
{
|
|
||||||
public BaseConfig()
|
|
||||||
{
|
|
||||||
AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default
|
|
||||||
AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default
|
|
||||||
AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
// See the LICENSE file in the project root for more information.
|
|
||||||
|
|
||||||
using BenchmarkDotNet.Environments;
|
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using BenchmarkDotNet.Toolchains.CsProj;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks.Configurations
|
|
||||||
{
|
|
||||||
public class RuntimeCompareConfig : BaseConfig
|
|
||||||
{
|
|
||||||
public RuntimeCompareConfig()
|
|
||||||
{
|
|
||||||
AddJob(Job.Default.WithRuntime(ClrRuntime.Net48));
|
|
||||||
AddJob(Job.Default.WithRuntime(CoreRuntime.Core50).WithToolchain(CsProjCoreToolchain.NetCoreApp50));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class LoggerBenchmark
|
public class LoggerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttNetNullLogger _nullLogger;
|
MqttNetNullLogger _nullLogger;
|
||||||
MqttNetSourceLogger _sourceNullLogger;
|
MqttNetSourceLogger _sourceNullLogger;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<DebugType>Full</DebugType>
|
<DebugType>Full</DebugType>
|
||||||
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
|
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||||
<LangVersion>7.2</LangVersion>
|
<LangVersion>7.2</LangVersion>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -26,9 +28,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.AspnetCore\MQTTnet.AspNetCore.csproj" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
<ProjectReference Include="..\MQTTnet.Tests\MQTTnet.Tests.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MemoryCopyBenchmark
|
||||||
|
{
|
||||||
|
const int max_length = 1024 * 8;
|
||||||
|
private byte[] source;
|
||||||
|
private byte[] target;
|
||||||
|
|
||||||
|
[Params(64 - 1, 128 - 1, 256 - 1, 512 - 1, 1024 - 1, 2048 - 1, 5096 - 1)]
|
||||||
|
public int Length { get; set; }
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
source = new byte[max_length];
|
||||||
|
target = new byte[max_length];
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public void Array_Copy()
|
||||||
|
{
|
||||||
|
Array.Copy(source, 0, target, 0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Memory_Copy()
|
||||||
|
{
|
||||||
|
MQTTnet.Internal.MqttMemoryHelper.Copy(source, 0, target, 0, Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using System;
|
using System;
|
||||||
@@ -8,14 +11,10 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Create a number of topics, publish, subscribe, and wait for response
|
|
||||||
/// </summary>
|
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageDeliveryBenchmark
|
public class MessageDeliveryBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
List<MqttApplicationMessage> _topicPublishMessages;
|
List<MqttApplicationMessage> _topicPublishMessages;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingBenchmark
|
public class MessageProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MessageProcessingMqttConnectionContextBenchmark
|
public class MessageProcessingMqttConnectionContextBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IWebHost _host;
|
IWebHost _host;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MqttBufferReaderBenchmark
|
||||||
|
{
|
||||||
|
byte[] _buffer;
|
||||||
|
int _bufferLength;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
var writer = new MqttBufferWriter(1024, 1024);
|
||||||
|
writer.WriteString("hgfjkdfkjlghfdjghdfljkdfhgdlkjfshgsldkfjghsdflkjghdsflkjhrstiuoghlkfjbhnfbutghjoiöjhklötnbhtroliöuhbjntluiobkjzbhtdrlskbhtruhjkfthgbkftgjhgfiklhotriuöhbjtrsioöbtrsötrhträhtrühjtriüoätrhjtsrölbktrbnhtrulöbionhströloubinströoliubhnsöotrunbtöroisntröointröioujhgötiohjgötorshjnbgtöorihbnjtröoihbjntröobntröoibntrjhötrohjbtröoihntröoibnrtoiöbtrjnboöitrhjtnriohötrhjtöroihjtroöihjtroösibntsroönbotöirsbntöoihjntröoihntroöbtrboöitrnhoöitrhjntröoishbnjtröosbhtröbntriohjtröoijtöoitbjöotibjnhöotirhbjntroöibhnjrtoöibnhtroöibnhtörsbnhtöoirbnhtöroibntoörhjnbträöbtrbträbtrbtirbätrsibohjntrsöiobthnjiohjsrtoib");
|
||||||
|
|
||||||
|
_buffer = writer.GetBuffer();
|
||||||
|
_bufferLength = writer.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Use_Span()
|
||||||
|
{
|
||||||
|
var span = _buffer.AsSpan(0, _bufferLength);
|
||||||
|
Encoding.UTF8.GetString(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void Use_Encoding()
|
||||||
|
{
|
||||||
|
Encoding.UTF8.GetString(_buffer, 0, _bufferLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.AspNetCore;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
@@ -9,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class MqttPacketReaderWriterBenchmark
|
public class MqttPacketReaderWriterBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
readonly byte[] _demoPayload = new byte[1024];
|
readonly byte[] _demoPayload = new byte[1024];
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public sealed class MqttTcpChannelBenchmark
|
public class MqttTcpChannelBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttChannel _serverChannel;
|
IMqttChannel _serverChannel;
|
||||||
|
|||||||
@@ -3,85 +3,140 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BenchmarkDotNet.Loggers;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
using MQTTnet.Benchmarks.Configurations;
|
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
static int _selectedBenchmarkIndex;
|
||||||
{
|
static List<Type> _benchmarks;
|
||||||
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
|
||||||
Console.WriteLine("--------------------------------------------------------");
|
|
||||||
Console.WriteLine("1 = MessageProcessingBenchmark");
|
|
||||||
Console.WriteLine("2 = SerializerBenchmark");
|
|
||||||
Console.WriteLine("3 = LoggerBenchmark");
|
|
||||||
Console.WriteLine("4 = TopicFilterComparerBenchmark");
|
|
||||||
Console.WriteLine("5 = ChannelAdapterBenchmark");
|
|
||||||
Console.WriteLine("6 = MqttTcpChannelBenchmark");
|
|
||||||
Console.WriteLine("7 = TcpPipesBenchmark");
|
|
||||||
Console.WriteLine("8 = MessageProcessingMqttConnectionContextBenchmark");
|
|
||||||
Console.WriteLine("9 = ServerProcessingBenchmark");
|
|
||||||
Console.WriteLine("a = MqttPacketReaderWriterBenchmark");
|
|
||||||
Console.WriteLine("b = RoundtripBenchmark");
|
|
||||||
Console.WriteLine("c = SubscribeBenchmark");
|
|
||||||
Console.WriteLine("d = UnsubscribeBenchmark");
|
|
||||||
Console.WriteLine("e = MessageDeliveryBenchmark");
|
|
||||||
Console.WriteLine("f = AsyncLockBenchmark");
|
|
||||||
|
|
||||||
var pressedKey = Console.ReadKey(true);
|
public static void Main(string[] arguments)
|
||||||
switch (pressedKey.KeyChar)
|
{
|
||||||
|
_benchmarks = CollectBenchmarks();
|
||||||
|
HandleArguments(arguments);
|
||||||
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
case '1':
|
RenderMenu();
|
||||||
BenchmarkRunner.Run<MessageProcessingBenchmark>();
|
|
||||||
break;
|
var key = Console.ReadKey(true);
|
||||||
case '2':
|
|
||||||
BenchmarkRunner.Run<SerializerBenchmark>();
|
if (key.Key == ConsoleKey.DownArrow)
|
||||||
break;
|
{
|
||||||
case '3':
|
_selectedBenchmarkIndex++;
|
||||||
BenchmarkRunner.Run<LoggerBenchmark>();
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
break;
|
{
|
||||||
case '4':
|
_selectedBenchmarkIndex = 0;
|
||||||
BenchmarkRunner.Run<TopicFilterComparerBenchmark>();
|
}
|
||||||
break;
|
}
|
||||||
case '5':
|
else if (key.Key == ConsoleKey.UpArrow)
|
||||||
BenchmarkRunner.Run<ChannelAdapterBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex--;
|
||||||
case '6':
|
if (_selectedBenchmarkIndex < 0)
|
||||||
BenchmarkRunner.Run<MqttTcpChannelBenchmark>();
|
{
|
||||||
break;
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
case '7':
|
}
|
||||||
BenchmarkRunner.Run<TcpPipesBenchmark>();
|
}
|
||||||
break;
|
else if (key.Key == ConsoleKey.Escape)
|
||||||
case '8':
|
{
|
||||||
BenchmarkRunner.Run<MessageProcessingMqttConnectionContextBenchmark>(new RuntimeCompareConfig());
|
Environment.Exit(0);
|
||||||
break;
|
}
|
||||||
case '9':
|
else if (key.Key == ConsoleKey.Enter)
|
||||||
BenchmarkRunner.Run<ServerProcessingBenchmark>();
|
{
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
BenchmarkRunner.Run(typeof(MqttPacketReaderWriterBenchmark));
|
|
||||||
break;
|
|
||||||
case 'b':
|
|
||||||
BenchmarkRunner.Run<RoundtripProcessingBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
BenchmarkRunner.Run<SubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
BenchmarkRunner.Run<UnsubscribeBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
BenchmarkRunner.Run<MessageDeliveryBenchmark>();
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
BenchmarkRunner.Run<AsyncLockBenchmark>();
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BenchmarkRunner.Run(_benchmarks[_selectedBenchmarkIndex]);
|
||||||
|
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine("Press any key to exit");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<Type> CollectBenchmarks()
|
||||||
|
{
|
||||||
|
var benchmarks = new List<Type>();
|
||||||
|
|
||||||
|
var types = typeof(Program).Assembly.GetExportedTypes();
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
if (typeof(BaseBenchmark).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
if (type != typeof(BaseBenchmark))
|
||||||
|
{
|
||||||
|
benchmarks.Add(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return benchmarks.OrderBy(b => b.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleArguments(string[] arguments)
|
||||||
|
{
|
||||||
|
if (arguments.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for preselection to avoid developer frustration.
|
||||||
|
|
||||||
|
if (int.TryParse(arguments[0], out var benchmarkIndex))
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = benchmarkIndex;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.FindIndex(b => b.Name.Equals(arguments[0]));
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex < 0)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex > _benchmarks.Count - 1)
|
||||||
|
{
|
||||||
|
_selectedBenchmarkIndex = _benchmarks.Count - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderMenu()
|
||||||
|
{
|
||||||
|
Console.Clear();
|
||||||
|
|
||||||
|
Console.WriteLine($"MQTTnet - Benchmarks ({TargetFrameworkProvider.TargetFramework})");
|
||||||
|
Console.WriteLine("-----------------------------------------------");
|
||||||
|
Console.ForegroundColor = ConsoleColor.Yellow;
|
||||||
|
Console.WriteLine("Press arrow keys for benchmark selection");
|
||||||
|
Console.WriteLine("Press Enter for benchmark execution");
|
||||||
|
Console.WriteLine("Press Esc for exit");
|
||||||
|
Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
for (var index = 0; index < _benchmarks.Count; index++)
|
||||||
|
{
|
||||||
|
var benchmark = _benchmarks[index];
|
||||||
|
|
||||||
|
if (_selectedBenchmarkIndex == index)
|
||||||
|
{
|
||||||
|
Console.Write("-> ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Write(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(benchmark.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
|
using MQTTnet.AspNetCore;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipelines;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MQTTnet.Benchmarks
|
||||||
|
{
|
||||||
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
|
[RPlotExporter, RankColumn]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class ReaderExtensionsBenchmark
|
||||||
|
{
|
||||||
|
MqttPacketFormatterAdapter mqttPacketFormatter;
|
||||||
|
MemoryStream stream;
|
||||||
|
|
||||||
|
[GlobalSetup]
|
||||||
|
public void GlobalSetup()
|
||||||
|
{
|
||||||
|
mqttPacketFormatter = new MqttPacketFormatterAdapter(MqttProtocolVersion.V311, new MqttBufferWriter(4096, 65535));
|
||||||
|
var mqttMessage = new MqttApplicationMessageBuilder()
|
||||||
|
.WithTopic("topic")
|
||||||
|
.WithPayload(new byte[10 * 1024])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var packet = MqttPacketFactories.Publish.Create(mqttMessage);
|
||||||
|
|
||||||
|
var buffer = mqttPacketFormatter.Encode(packet);
|
||||||
|
stream = new MemoryStream();
|
||||||
|
stream.Write(buffer.Packet);
|
||||||
|
stream.Write(buffer.Payload);
|
||||||
|
mqttPacketFormatter.Cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public async Task Before()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var input = PipeReader.Create(stream);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ReadResult readResult;
|
||||||
|
var readTask = input.ReadAsync();
|
||||||
|
if (readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = readTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (ReaderExtensions_Before.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public async Task After()
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
var input = PipeReader.Create(stream);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
ReadResult readResult;
|
||||||
|
var readTask = input.ReadAsync();
|
||||||
|
if (readTask.IsCompleted)
|
||||||
|
{
|
||||||
|
readResult = readTask.Result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readResult = await readTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = readResult.Buffer;
|
||||||
|
|
||||||
|
var consumed = buffer.Start;
|
||||||
|
var observed = buffer.Start;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!buffer.IsEmpty)
|
||||||
|
{
|
||||||
|
if (ReaderExtensions.TryDecode(mqttPacketFormatter, buffer, out var packet, out consumed, out observed, out var received))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (readResult.IsCompleted)
|
||||||
|
{
|
||||||
|
throw new MqttCommunicationException("Connection Aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// The buffer was sliced up to where it was consumed, so we can just advance to the start.
|
||||||
|
// We mark examined as buffer.End so that if we didn't receive a full frame, we'll wait for more data
|
||||||
|
// before yielding the read again.
|
||||||
|
input.AdvanceTo(consumed, observed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ReaderExtensions_Before
|
||||||
|
{
|
||||||
|
public static bool TryDecode(MqttPacketFormatterAdapter formatter,
|
||||||
|
in ReadOnlySequence<byte> input,
|
||||||
|
out MqttPacket packet,
|
||||||
|
out SequencePosition consumed,
|
||||||
|
out SequencePosition observed,
|
||||||
|
out int bytesRead)
|
||||||
|
{
|
||||||
|
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||||
|
|
||||||
|
packet = null;
|
||||||
|
consumed = input.Start;
|
||||||
|
observed = input.End;
|
||||||
|
bytesRead = 0;
|
||||||
|
var copy = input;
|
||||||
|
|
||||||
|
if (copy.Length < 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fixedHeader = copy.First.Span[0];
|
||||||
|
if (!TryReadBodyLength(ref copy, out int headerLength, out var bodyLength))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy.Length < bodyLength)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bodySlice = copy.Slice(0, bodyLength);
|
||||||
|
var buffer = GetMemory(bodySlice).ToArray();
|
||||||
|
|
||||||
|
var receivedMqttPacket = new ReceivedMqttPacket(fixedHeader, new ArraySegment<byte>(buffer, 0, buffer.Length), buffer.Length + 2);
|
||||||
|
|
||||||
|
if (formatter.ProtocolVersion == MqttProtocolVersion.Unknown)
|
||||||
|
{
|
||||||
|
formatter.DetectProtocolVersion(receivedMqttPacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = formatter.Decode(receivedMqttPacket);
|
||||||
|
consumed = bodySlice.End;
|
||||||
|
observed = bodySlice.End;
|
||||||
|
bytesRead = headerLength + bodyLength;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReadOnlyMemory<byte> GetMemory(in ReadOnlySequence<byte> input)
|
||||||
|
{
|
||||||
|
if (input.IsSingleSegment)
|
||||||
|
{
|
||||||
|
return input.First;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be rare
|
||||||
|
return input.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadBodyLength(ref ReadOnlySequence<byte> input, out int headerLength, out int bodyLength)
|
||||||
|
{
|
||||||
|
// Alorithm taken from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html.
|
||||||
|
var multiplier = 1;
|
||||||
|
var value = 0;
|
||||||
|
byte encodedByte;
|
||||||
|
var index = 1;
|
||||||
|
headerLength = 0;
|
||||||
|
bodyLength = 0;
|
||||||
|
|
||||||
|
var temp = GetMemory(input.Slice(0, Math.Min(5, input.Length))).Span;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (index == temp.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedByte = temp[index];
|
||||||
|
index++;
|
||||||
|
|
||||||
|
value += (byte)(encodedByte & 127) * multiplier;
|
||||||
|
if (multiplier > 128 * 128 * 128)
|
||||||
|
{
|
||||||
|
throw new MqttProtocolViolationException($"Remaining length is invalid (Data={string.Join(",", temp.Slice(1, index).ToArray())}).");
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplier *= 128;
|
||||||
|
} while ((encodedByte & 128) != 0);
|
||||||
|
|
||||||
|
input = input.Slice(index);
|
||||||
|
|
||||||
|
headerLength = index;
|
||||||
|
bodyLength = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class RoundtripProcessingBenchmark
|
public class RoundtripProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
@@ -8,7 +12,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter, RankColumn]
|
[RPlotExporter, RankColumn]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class ServerProcessingBenchmark
|
public class ServerProcessingBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
public void GlobalSetup()
|
public void GlobalSetup()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using BenchmarkDotNet.Jobs;
|
using BenchmarkDotNet.Jobs;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -8,7 +12,7 @@ using System.Linq;
|
|||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class SubscribeBenchmark
|
public class SubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
{
|
{
|
||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TcpPipesBenchmark
|
public class TcpPipesBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
IDuplexPipe _client;
|
IDuplexPipe _client;
|
||||||
IDuplexPipe _server;
|
IDuplexPipe _server;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
[SimpleJob(RuntimeMoniker.Net60)]
|
[SimpleJob(RuntimeMoniker.Net60)]
|
||||||
[RPlotExporter]
|
[RPlotExporter]
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class TopicFilterComparerBenchmark
|
public class TopicFilterComparerBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
static readonly char[] TopicLevelSeparator = { '/' };
|
static readonly char[] TopicLevelSeparator = { '/' };
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,30 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
public static void Generate(
|
public static void Generate(
|
||||||
int numPublishers, int numTopicsPerPublisher,
|
int numPublishers,
|
||||||
out Dictionary<string, List<string>> topicsByPublisher,
|
int numTopicsPerPublisher,
|
||||||
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
out Dictionary<string, List<string>> topicsByPublisher,
|
||||||
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher
|
out Dictionary<string, List<string>> singleWildcardTopicsByPublisher,
|
||||||
)
|
out Dictionary<string, List<string>> multiWildcardTopicsByPublisher)
|
||||||
{
|
{
|
||||||
topicsByPublisher = new Dictionary<string, List<string>>();
|
topicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
singleWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
multiWildcardTopicsByPublisher = new Dictionary<string, List<string>>();
|
||||||
|
|
||||||
// Find some reasonable distribution across three topic levels
|
// Find some reasonable distribution across three topic levels
|
||||||
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, (1.0 / 3.0));
|
var topicsPerLevel = (int)Math.Pow(numTopicsPerPublisher, 1.0 / 3.0);
|
||||||
if (topicsPerLevel <= 0)
|
if (topicsPerLevel <= 0)
|
||||||
{
|
{
|
||||||
topicsPerLevel = 1;
|
topicsPerLevel = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int numLevel1Topics = topicsPerLevel;
|
var numLevel1Topics = topicsPerLevel;
|
||||||
int numLevel2Topics = topicsPerLevel;
|
var numLevel2Topics = topicsPerLevel;
|
||||||
|
|
||||||
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
var maxNumLevel3Topics = 1 + (int)((double)numTopicsPerPublisher / numLevel1Topics / numLevel2Topics);
|
||||||
if (maxNumLevel3Topics <= 0)
|
if (maxNumLevel3Topics <= 0)
|
||||||
@@ -35,7 +34,7 @@ namespace MQTTnet.Benchmarks
|
|||||||
|
|
||||||
for (var p = 0; p < numPublishers; ++p)
|
for (var p = 0; p < numPublishers; ++p)
|
||||||
{
|
{
|
||||||
int publisherTopicCount = 0;
|
var publisherTopicCount = 0;
|
||||||
var publisherName = "pub" + p;
|
var publisherName = "pub" + p;
|
||||||
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
for (var l1 = 0; l1 < numLevel1Topics; ++l1)
|
||||||
{
|
{
|
||||||
@@ -44,19 +43,22 @@ namespace MQTTnet.Benchmarks
|
|||||||
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
for (var l3 = 0; l3 < maxNumLevel3Topics; ++l3)
|
||||||
{
|
{
|
||||||
if (publisherTopicCount >= numTopicsPerPublisher)
|
if (publisherTopicCount >= numTopicsPerPublisher)
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var topic = string.Format("{0}/building{1}/level{2}/sensor{3}", publisherName, l1 + 1, l2 + 1, l3 + 1);
|
var topic = $"{publisherName}/building{l1 + 1}/level{l2 + 1}/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
AddPublisherTopic(publisherName, topic, topicsByPublisher);
|
||||||
|
|
||||||
if (l2 == 0)
|
if (l2 == 0)
|
||||||
{
|
{
|
||||||
var singleWildcardTopic = string.Format("{0}/building{1}/+/sensor{2}", publisherName, l1 + 1, l3 + 1);
|
var singleWildcardTopic = $"{publisherName}/building{l1 + 1}/+/sensor{l3 + 1}";
|
||||||
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, singleWildcardTopic, singleWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
if ((l1 == 0) && (l3 == 0))
|
|
||||||
|
if (l1 == 0 && l3 == 0)
|
||||||
{
|
{
|
||||||
var multiWildcardTopic = string.Format("{0}/+/level{1}/+", publisherName, l2 + 1);
|
var multiWildcardTopic = $"{publisherName}/+/level{l2 + 1}/+";
|
||||||
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
AddPublisherTopic(publisherName, multiWildcardTopic, multiWildcardTopicsByPublisher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,8 @@ namespace MQTTnet.Benchmarks
|
|||||||
topicList = new List<string>();
|
topicList = new List<string>();
|
||||||
topicsByPublisher.Add(publisherName, topicList);
|
topicsByPublisher.Add(publisherName, topicList);
|
||||||
}
|
}
|
||||||
|
|
||||||
topicList.Add(topic);
|
topicList.Add(topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -7,7 +11,7 @@ using System.Linq;
|
|||||||
namespace MQTTnet.Benchmarks
|
namespace MQTTnet.Benchmarks
|
||||||
{
|
{
|
||||||
[MemoryDiagnoser]
|
[MemoryDiagnoser]
|
||||||
public class UnsubscribeBenchmark
|
public class UnsubscribeBenchmark : BaseBenchmark
|
||||||
{
|
{
|
||||||
MqttServer _mqttServer;
|
MqttServer _mqttServer;
|
||||||
IMqttClient _mqttClient;
|
IMqttClient _mqttClient;
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="BenchmarkDotNet" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Core" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.10.14" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.Common" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.CodeAnalysis.CSharp" version="2.6.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.InternalAbstractions" version="1.0.0" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.DotNet.PlatformAbstractions" version="1.1.1" targetFramework="net462" />
|
|
||||||
<package id="Microsoft.Win32.Registry" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.AppContext" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Collections.Immutable" version="1.3.1" targetFramework="net462" />
|
|
||||||
<package id="System.Console" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.FileVersionInfo" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.StackTrace" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Globalization" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.Compression" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Reflection.Metadata" version="1.4.2" targetFramework="net462" />
|
|
||||||
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.CodePages" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath" version="4.3.0" targetFramework="net462" />
|
|
||||||
<package id="System.Xml.XPath.XDocument" version="4.3.0" targetFramework="net462" />
|
|
||||||
</packages>
|
|
||||||
@@ -43,7 +43,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
Task StartAsync(ManagedMqttClientOptions options);
|
Task StartAsync(ManagedMqttClientOptions options);
|
||||||
|
|
||||||
Task StopAsync();
|
Task StopAsync(bool cleanDisconnect = true);
|
||||||
|
|
||||||
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</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -160,7 +161,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.Topic);
|
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
|
||||||
|
|
||||||
ManagedMqttApplicationMessage removedMessage = null;
|
ManagedMqttApplicationMessage removedMessage = null;
|
||||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
||||||
@@ -255,10 +256,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
_logger.Info("Started");
|
_logger.Info("Started");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync(bool cleanDisconnect = true)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
_isCleanDisconnect = cleanDisconnect;
|
||||||
|
|
||||||
StopPublishing();
|
StopPublishing();
|
||||||
StopMaintainingConnection();
|
StopMaintainingConnection();
|
||||||
|
|
||||||
@@ -382,9 +385,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
if (_isCleanDisconnect)
|
||||||
{
|
{
|
||||||
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||||
|
{
|
||||||
|
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory factory, IMqttClient mqttClient)
|
||||||
{
|
{
|
||||||
return factory.CreateMqttRpcClient(mqttClient, new MqttRpcClientOptions
|
return factory.CreateMqttRpcClient(
|
||||||
{
|
mqttClient,
|
||||||
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
new MqttRpcClientOptions
|
||||||
});
|
{
|
||||||
|
TopicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
public static IMqttRpcClient CreateMqttRpcClient(this MqttFactory _, IMqttClient mqttClient, MqttRpcClientOptions rpcClientOptions)
|
||||||
|
|||||||
@@ -2,23 +2,24 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using MQTTnet.Exceptions;
|
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.Rpc
|
namespace MQTTnet.Extensions.Rpc
|
||||||
{
|
{
|
||||||
public sealed class MqttRpcClient : IMqttRpcClient
|
public sealed class MqttRpcClient : IMqttRpcClient
|
||||||
{
|
{
|
||||||
readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
|
|
||||||
readonly IMqttClient _mqttClient;
|
readonly IMqttClient _mqttClient;
|
||||||
readonly MqttRpcClientOptions _options;
|
readonly MqttRpcClientOptions _options;
|
||||||
|
|
||||||
|
readonly ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, AsyncTaskCompletionSource<byte[]>>();
|
||||||
|
|
||||||
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
public MqttRpcClient(IMqttClient mqttClient, MqttRpcClientOptions options)
|
||||||
{
|
{
|
||||||
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
_mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
@@ -27,6 +28,18 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
_mqttClient.ApplicationMessageReceivedAsync += HandleApplicationMessageReceivedAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
||||||
|
|
||||||
|
foreach (var tcs in _waitingCalls)
|
||||||
|
{
|
||||||
|
tcs.Value.TrySetCanceled();
|
||||||
|
}
|
||||||
|
|
||||||
|
_waitingCalls.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
using (var timeoutToken = new CancellationTokenSource(timeout))
|
using (var timeoutToken = new CancellationTokenSource(timeout))
|
||||||
@@ -49,15 +62,13 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
public async Task<byte[]> ExecuteAsync(string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (methodName == null) throw new ArgumentNullException(nameof(methodName));
|
if (methodName == null)
|
||||||
|
|
||||||
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(new TopicGenerationContext
|
|
||||||
{
|
{
|
||||||
MethodName = methodName,
|
throw new ArgumentNullException(nameof(methodName));
|
||||||
QualityOfServiceLevel = qualityOfServiceLevel,
|
}
|
||||||
MqttClient = _mqttClient,
|
|
||||||
Options = _options
|
var context = new TopicGenerationContext(_mqttClient, _options, methodName, qualityOfServiceLevel);
|
||||||
});
|
var topicNames = _options.TopicGenerationStrategy.CreateRpcTopics(context);
|
||||||
|
|
||||||
var requestTopic = topicNames.RequestTopic;
|
var requestTopic = topicNames.RequestTopic;
|
||||||
var responseTopic = topicNames.ResponseTopic;
|
var responseTopic = topicNames.ResponseTopic;
|
||||||
@@ -72,8 +83,7 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
throw new MqttProtocolViolationException("RPC response topic is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestMessage = new MqttApplicationMessageBuilder()
|
var requestMessage = new MqttApplicationMessageBuilder().WithTopic(requestTopic)
|
||||||
.WithTopic(requestTopic)
|
|
||||||
.WithPayload(payload)
|
.WithPayload(payload)
|
||||||
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
.WithQualityOfServiceLevel(qualityOfServiceLevel)
|
||||||
.WithResponseTopic(responseTopic)
|
.WithResponseTopic(responseTopic)
|
||||||
@@ -81,25 +91,23 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#if NET452
|
var awaitable = new AsyncTaskCompletionSource<byte[]>();
|
||||||
var awaitable = new TaskCompletionSource<byte[]>();
|
|
||||||
#else
|
|
||||||
var awaitable = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
if (!_waitingCalls.TryAdd(responseTopic, awaitable))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var subscribeOptions = new MqttClientSubscribeOptionsBuilder()
|
var subscribeOptions = new MqttClientSubscribeOptionsBuilder().WithTopicFilter(responseTopic, qualityOfServiceLevel).Build();
|
||||||
.WithTopicFilter(responseTopic, qualityOfServiceLevel)
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false);
|
||||||
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); }))
|
using (cancellationToken.Register(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
awaitable.TrySetCanceled();
|
||||||
|
}))
|
||||||
{
|
{
|
||||||
return await awaitable.Task.ConfigureAwait(false);
|
return await awaitable.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -107,8 +115,8 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_waitingCalls.TryRemove(responseTopic, out _);
|
_waitingCalls.TryRemove(responseTopic, out _);
|
||||||
|
|
||||||
await _mqttClient.UnsubscribeAsync(responseTopic).ConfigureAwait(false);
|
await _mqttClient.UnsubscribeAsync(responseTopic, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,28 +127,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NET452
|
|
||||||
Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload));
|
|
||||||
#else
|
|
||||||
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set this message to handled to that other code can avoid execution etc.
|
// Set this message to handled to that other code can avoid execution etc.
|
||||||
eventArgs.IsHandled = true;
|
eventArgs.IsHandled = true;
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_mqttClient.ApplicationMessageReceivedAsync -= HandleApplicationMessageReceivedAsync;
|
|
||||||
|
|
||||||
foreach (var tcs in _waitingCalls)
|
|
||||||
{
|
|
||||||
tcs.Value.TrySetCanceled();
|
|
||||||
}
|
|
||||||
|
|
||||||
_waitingCalls.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,13 +10,6 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
IMqttRpcClientTopicGenerationStrategy _topicGenerationStrategy = new DefaultMqttRpcClientTopicGenerationStrategy();
|
||||||
|
|
||||||
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
|
||||||
{
|
|
||||||
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttRpcClientOptions Build()
|
public MqttRpcClientOptions Build()
|
||||||
{
|
{
|
||||||
return new MqttRpcClientOptions
|
return new MqttRpcClientOptions
|
||||||
@@ -24,5 +17,12 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
TopicGenerationStrategy = _topicGenerationStrategy
|
TopicGenerationStrategy = _topicGenerationStrategy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MqttRpcClientOptionsBuilder WithTopicGenerationStrategy(IMqttRpcClientTopicGenerationStrategy value)
|
||||||
|
{
|
||||||
|
_topicGenerationStrategy = value ?? throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+5
@@ -10,6 +10,11 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
{
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
if (context.MethodName.Contains("/") || context.MethodName.Contains("+") || context.MethodName.Contains("#"))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The method name cannot contain /, + or #.");
|
throw new ArgumentException("The method name cannot contain /, + or #.");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
@@ -9,20 +10,29 @@ namespace MQTTnet.Extensions.Rpc
|
|||||||
{
|
{
|
||||||
public sealed class TopicGenerationContext
|
public sealed class TopicGenerationContext
|
||||||
{
|
{
|
||||||
public string MethodName { get; set; }
|
public TopicGenerationContext(IMqttClient mqttClient, MqttRpcClientOptions options, string methodName, MqttQualityOfServiceLevel qualityOfServiceLevel)
|
||||||
|
{
|
||||||
|
MethodName = methodName ?? throw new ArgumentNullException(nameof(methodName));
|
||||||
|
QualityOfServiceLevel = qualityOfServiceLevel;
|
||||||
|
MqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
|
||||||
|
Options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MethodName { get; }
|
||||||
|
|
||||||
|
public IMqttClient MqttClient { get; }
|
||||||
|
|
||||||
|
public MqttRpcClientOptions Options { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the quality of service level.
|
/// Gets or sets the quality of service level.
|
||||||
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
|
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message
|
||||||
/// There are 3 QoS levels in MQTT:
|
/// that defines the guarantee of delivery for a specific message.
|
||||||
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
/// There are 3 QoS levels in MQTT:
|
||||||
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
/// - At most once (0): Message gets delivered no time, once or multiple times.
|
||||||
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
/// - At least once (1): Message gets delivered at least once (one time or more often).
|
||||||
|
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; }
|
||||||
|
|
||||||
public IMqttClient MqttClient { get; set; }
|
|
||||||
|
|
||||||
public MqttRpcClientOptions Options { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,8 @@
|
|||||||
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
<PackageReleaseNotes>For release notes please go to MQTTnet release notes (https://www.nuget.org/packages/MQTTnet/).</PackageReleaseNotes>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>1591</NoWarn>
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
|
<LangVersion>7.3</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'uap10.0'">
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
public static MqttFactory UseWebSocket4Net(this MqttFactory mqttFactory)
|
||||||
{
|
{
|
||||||
if (mqttFactory == null) throw new ArgumentNullException(nameof(mqttFactory));
|
if (mqttFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(mqttFactory));
|
||||||
|
}
|
||||||
|
|
||||||
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
return mqttFactory.UseClientAdapterFactory(new WebSocket4NetMqttClientAdapterFactory());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
@@ -17,14 +18,14 @@ using MQTTnet.Exceptions;
|
|||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using SuperSocket.ClientEngine;
|
using SuperSocket.ClientEngine;
|
||||||
using WebSocket4Net;
|
using WebSocket4Net;
|
||||||
|
using DataReceivedEventArgs = WebSocket4Net.DataReceivedEventArgs;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.WebSocket4Net
|
namespace MQTTnet.Extensions.WebSocket4Net
|
||||||
{
|
{
|
||||||
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
public sealed class WebSocket4NetMqttChannel : IMqttChannel
|
||||||
{
|
{
|
||||||
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
|
||||||
|
|
||||||
readonly MqttClientOptions _clientOptions;
|
readonly MqttClientOptions _clientOptions;
|
||||||
|
readonly BlockingCollection<byte> _receiveBuffer = new BlockingCollection<byte>();
|
||||||
readonly MqttClientWebSocketOptions _webSocketOptions;
|
readonly MqttClientWebSocketOptions _webSocketOptions;
|
||||||
|
|
||||||
WebSocket _webSocket;
|
WebSocket _webSocket;
|
||||||
@@ -35,13 +36,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
_webSocketOptions = webSocketOptions ?? throw new ArgumentNullException(nameof(webSocketOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public X509Certificate2 ClientCertificate { get; }
|
||||||
|
|
||||||
public string Endpoint => _webSocketOptions.Uri;
|
public string Endpoint => _webSocketOptions.Uri;
|
||||||
|
|
||||||
public bool IsSecureConnection { get; private set; }
|
public bool IsSecureConnection { get; private set; }
|
||||||
|
|
||||||
public X509Certificate2 ClientCertificate { get; }
|
public Task ConnectAsync(CancellationToken cancellationToken)
|
||||||
|
|
||||||
public async Task ConnectAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var uri = _webSocketOptions.Uri;
|
var uri = _webSocketOptions.Uri;
|
||||||
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -55,11 +56,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
uri = "wss://" + uri;
|
uri = "wss://" + uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
#if NET48 || NETCOREAPP3_0_OR_GREATER
|
||||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||||
#else
|
#else
|
||||||
var sslProtocols = _webSocketOptions?.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
var sslProtocols = _webSocketOptions.TlsOptions?.SslProtocol ?? SslProtocols.Tls12 | (SslProtocols)0x00003000 /*Tls13*/;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
var subProtocol = _webSocketOptions.SubProtocols.FirstOrDefault() ?? string.Empty;
|
||||||
@@ -90,7 +93,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
var receiveBufferSize = 0;
|
var receiveBufferSize = 0;
|
||||||
|
|
||||||
var certificates = new X509CertificateCollection();
|
var certificates = new X509CertificateCollection();
|
||||||
if (_webSocketOptions?.TlsOptions?.Certificates != null)
|
if (_webSocketOptions.TlsOptions?.Certificates != null)
|
||||||
{
|
{
|
||||||
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
foreach (var certificate in _webSocketOptions.TlsOptions.Certificates)
|
||||||
{
|
{
|
||||||
@@ -107,15 +110,13 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
NoDelay = true,
|
NoDelay = true,
|
||||||
Security =
|
Security =
|
||||||
{
|
{
|
||||||
AllowUnstrustedCertificate = _webSocketOptions?.TlsOptions?.AllowUntrustedCertificates == true,
|
AllowUnstrustedCertificate = _webSocketOptions.TlsOptions?.AllowUntrustedCertificates == true,
|
||||||
AllowCertificateChainErrors = _webSocketOptions?.TlsOptions?.IgnoreCertificateChainErrors == true,
|
AllowCertificateChainErrors = _webSocketOptions.TlsOptions?.IgnoreCertificateChainErrors == true,
|
||||||
Certificates = certificates
|
Certificates = certificates
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await ConnectInternalAsync(cancellationToken).ConfigureAwait(false);
|
return ConnectInternalAsync(cancellationToken);
|
||||||
|
|
||||||
IsSecureConnection = uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task DisconnectAsync(CancellationToken cancellationToken)
|
public Task DisconnectAsync(CancellationToken cancellationToken)
|
||||||
@@ -124,10 +125,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
_webSocket.Close();
|
_webSocket.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_webSocket == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_webSocket.DataReceived -= OnDataReceived;
|
||||||
|
_webSocket.Error -= OnError;
|
||||||
|
_webSocket.Dispose();
|
||||||
|
_webSocket = null;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var readBytes = 0;
|
var readBytes = 0;
|
||||||
@@ -161,75 +175,55 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
return CompletedTask.Instance;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_webSocket == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_webSocket.DataReceived -= OnDataReceived;
|
|
||||||
_webSocket.Error -= OnError;
|
|
||||||
_webSocket.Dispose();
|
|
||||||
_webSocket = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void OnError(object sender, ErrorEventArgs e)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.Write(e.Exception.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
|
||||||
{
|
|
||||||
foreach (var @byte in e.Data)
|
|
||||||
{
|
|
||||||
_receiveBuffer.Add(@byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
async Task ConnectInternalAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_webSocket.Error += OnError;
|
_webSocket.Error += OnError;
|
||||||
_webSocket.DataReceived += OnDataReceived;
|
_webSocket.DataReceived += OnDataReceived;
|
||||||
|
|
||||||
var taskCompletionSource = new TaskCompletionSource<Exception>();
|
var promise = new AsyncTaskCompletionSource<bool>();
|
||||||
|
|
||||||
void ErrorHandler(object sender, ErrorEventArgs e)
|
void ErrorHandler(object sender, ErrorEventArgs e)
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult(e.Exception);
|
promise.TrySetException(e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuccessHandler(object sender, EventArgs e)
|
void OpenedHandler(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
taskCompletionSource.TrySetResult(null);
|
promise.TrySetResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClosedHandler(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
promise.TrySetException(new MqttCommunicationException("Connection closed."));
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_webSocket.Opened += SuccessHandler;
|
_webSocket.Opened += OpenedHandler;
|
||||||
|
_webSocket.Closed += ClosedHandler;
|
||||||
_webSocket.Error += ErrorHandler;
|
_webSocket.Error += ErrorHandler;
|
||||||
|
|
||||||
#pragma warning disable AsyncFixer02 // Long-running or blocking operations inside an async method
|
using (var timeout = new CancellationTokenSource(_clientOptions.Timeout))
|
||||||
_webSocket.Open();
|
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeout.Token, cancellationToken))
|
||||||
#pragma warning restore AsyncFixer02 // Long-running or blocking operations inside an async method
|
|
||||||
|
|
||||||
using (var timeoutCts = new CancellationTokenSource(_clientOptions.Timeout))
|
|
||||||
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken))
|
|
||||||
{
|
{
|
||||||
using (linkedCts.Token.Register(() => taskCompletionSource.TrySetCanceled()))
|
using (linkedCts.Token.Register(() => promise.TrySetCanceled()))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
// OpenAsync does basically the same but without support for a cancellation token.
|
||||||
|
// So we do it on our own!
|
||||||
|
_webSocket.Open();
|
||||||
|
|
||||||
|
await promise.Task.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var timeoutReached = timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
var timeoutReached = timeout.IsCancellationRequested && !cancellationToken.IsCancellationRequested;
|
||||||
if (timeoutReached)
|
if (timeoutReached)
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationTimedOutException(exception);
|
throw new MqttCommunicationTimedOutException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is AuthenticationException authenticationException)
|
if (exception is AuthenticationException authenticationException)
|
||||||
{
|
{
|
||||||
throw new MqttCommunicationException(authenticationException.InnerException);
|
throw new MqttCommunicationException(authenticationException.InnerException);
|
||||||
@@ -251,9 +245,23 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_webSocket.Opened -= SuccessHandler;
|
_webSocket.Opened -= OpenedHandler;
|
||||||
|
_webSocket.Closed -= ClosedHandler;
|
||||||
_webSocket.Error -= ErrorHandler;
|
_webSocket.Error -= ErrorHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnDataReceived(object sender, DataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var @byte in e.Data)
|
||||||
|
{
|
||||||
|
_receiveBuffer.Add(@byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnError(object sender, ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
Debug.Write(e.Exception.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Adapter;
|
|
||||||
using MQTTnet.Formatter;
|
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using System;
|
using System;
|
||||||
|
using MQTTnet.Adapter;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Implementations;
|
||||||
|
|
||||||
namespace MQTTnet.Extensions.WebSocket4Net
|
namespace MQTTnet.Extensions.WebSocket4Net
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,10 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
public IMqttChannelAdapter CreateClientAdapter(MqttClientOptions options, MqttPacketInspector packetInspector, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
if (options == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
}
|
||||||
|
|
||||||
switch (options.ChannelOptions)
|
switch (options.ChannelOptions)
|
||||||
{
|
{
|
||||||
@@ -23,7 +26,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new MqttTcpChannel(options),
|
new MqttTcpChannel(options),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4096, 65535)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
packetInspector,
|
||||||
logger);
|
logger);
|
||||||
}
|
}
|
||||||
@@ -32,16 +35,16 @@ namespace MQTTnet.Extensions.WebSocket4Net
|
|||||||
{
|
{
|
||||||
return new MqttChannelAdapter(
|
return new MqttChannelAdapter(
|
||||||
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
new WebSocket4NetMqttChannel(options, webSocketOptions),
|
||||||
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(4068, 65535)),
|
new MqttPacketFormatterAdapter(options.ProtocolVersion, new MqttBufferWriter(options.WriterBufferSize, options.WriterBufferSizeMax)),
|
||||||
packetInspector,
|
packetInspector,
|
||||||
logger);
|
logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,11 @@
|
|||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using MQTTnet.Client;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
@@ -17,10 +18,6 @@ namespace MQTTnet.TestApp
|
|||||||
{
|
{
|
||||||
public static async Task RunAsync()
|
public static async Task RunAsync()
|
||||||
{
|
{
|
||||||
// MqttNetConsoleLogger.ForwardToConsole();
|
|
||||||
|
|
||||||
// For most of these connections to work, set output target to Net5.0.
|
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
// TLS13 is only available in Net5.0
|
// TLS13 is only available in Net5.0
|
||||||
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls13 = new MqttClientOptionsBuilderTlsParameters
|
||||||
@@ -31,7 +28,6 @@ namespace MQTTnet.TestApp
|
|||||||
CertificateValidationHandler = w => true
|
CertificateValidationHandler = w => true
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
// Also defining TLS12 for servers that don't seem no to support TLS13.
|
||||||
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
var unsafeTls12 = new MqttClientOptionsBuilderTlsParameters
|
||||||
{
|
{
|
||||||
@@ -42,33 +38,37 @@ namespace MQTTnet.TestApp
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mqtt.eclipseprojects.io
|
// mqtt.eclipseprojects.io
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883)
|
"mqtt.eclipseprojects.io TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("mqtt.eclipseprojects.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt")
|
"mqtt.eclipseprojects.io WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:80/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13",
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync("mqtt.eclipseprojects.io WS TLS13 (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("mqtt.eclipseprojects.io:443/mqtt")
|
||||||
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build(),
|
||||||
|
true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// test.mosquitto.org
|
// test.mosquitto.org
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883)
|
"test.mosquitto.org TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP - Authenticated",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884)
|
"test.mosquitto.org TCP - Authenticated",
|
||||||
.WithCredentials("rw", "readwrite")
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 1884).WithCredentials("rw", "readwrite").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883)
|
"test.mosquitto.org TCP TLS12",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
await ExecuteTestAsync("test.mosquitto.org TCP TLS13",
|
||||||
@@ -76,27 +76,40 @@ namespace MQTTnet.TestApp
|
|||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org TCP TLS12 - Authenticated",
|
await ExecuteTestAsync(
|
||||||
|
"test.mosquitto.org TCP TLS12 - Authenticated",
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
new MqttClientOptionsBuilder().WithTcpServer("test.mosquitto.org", 8885)
|
||||||
.WithCredentials("rw", "readwrite")
|
.WithCredentials("rw", "readwrite")
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311)
|
||||||
|
.WithTls(unsafeTls12)
|
||||||
|
.Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt")
|
"test.mosquitto.org WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("test.mosquitto.org WS TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt")
|
"test.mosquitto.org WS (WebSocket4Net)",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8080/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"test.mosquitto.org WS TLS12",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
|
// await ExecuteTestAsync(
|
||||||
|
// "test.mosquitto.org WS TLS12 (WebSocket4Net)",
|
||||||
|
// new MqttClientOptionsBuilder().WithWebSocketServer("test.mosquitto.org:8081/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||||
|
// true);
|
||||||
|
|
||||||
// broker.emqx.io
|
// broker.emqx.io
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883)
|
"broker.emqx.io TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883)
|
"broker.emqx.io TCP TLS12",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.emqx.io", 8883).WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
#if NET5_0_OR_GREATER
|
#if NET5_0_OR_GREATER
|
||||||
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
await ExecuteTestAsync("broker.emqx.io TCP TLS13",
|
||||||
@@ -104,23 +117,37 @@ namespace MQTTnet.TestApp
|
|||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls13).Build());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt")
|
"broker.emqx.io WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8083/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.emqx.io WS TLS12",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt")
|
"broker.emqx.io WS (WebSocket4Net)",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.emqx.io WS TLS12",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.emqx.io WS TLS12 (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.emqx.io:8084/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).WithTls(unsafeTls12).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
// broker.hivemq.com
|
// broker.hivemq.com
|
||||||
await ExecuteTestAsync("broker.hivemq.com TCP",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883)
|
"broker.hivemq.com TCP",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com", 1883).WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
await ExecuteTestAsync("broker.hivemq.com WS",
|
await ExecuteTestAsync(
|
||||||
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt")
|
"broker.hivemq.com WS",
|
||||||
.WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build());
|
||||||
|
|
||||||
|
await ExecuteTestAsync(
|
||||||
|
"broker.hivemq.com WS (WebSocket4Net)",
|
||||||
|
new MqttClientOptionsBuilder().WithWebSocketServer("broker.hivemq.com:8000/mqtt").WithProtocolVersion(MqttProtocolVersion.V311).Build(),
|
||||||
|
true);
|
||||||
|
|
||||||
// mqtt.swifitch.cz: Does not seem to operate any more
|
// mqtt.swifitch.cz: Does not seem to operate any more
|
||||||
|
|
||||||
@@ -130,36 +157,43 @@ namespace MQTTnet.TestApp
|
|||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task ExecuteTestAsync(string name, MqttClientOptions options)
|
static async Task ExecuteTestAsync(string name, MqttClientOptions options, bool useWebSocket4Net = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
Write("Testing '" + name + "'... ", ConsoleColor.Gray);
|
||||||
|
|
||||||
var factory = new MqttFactory();
|
var factory = new MqttFactory();
|
||||||
//factory.UseWebSocket4Net();
|
if (useWebSocket4Net)
|
||||||
var client = factory.CreateMqttClient();
|
|
||||||
var topic = Guid.NewGuid().ToString();
|
|
||||||
|
|
||||||
MqttApplicationMessage receivedMessage = null;
|
|
||||||
client.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
{
|
||||||
receivedMessage = e.ApplicationMessage;
|
factory.UseWebSocket4Net();
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await client.ConnectAsync(options);
|
|
||||||
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce);
|
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
|
||||||
|
|
||||||
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
|
||||||
{
|
|
||||||
throw new Exception("Message invalid.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.UnsubscribeAsync(topic);
|
using (var client = factory.CreateMqttClient())
|
||||||
await client.DisconnectAsync();
|
{
|
||||||
|
MqttApplicationMessage receivedMessage = null;
|
||||||
|
client.ApplicationMessageReceivedAsync += e =>
|
||||||
|
{
|
||||||
|
receivedMessage = e.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var topic = Guid.NewGuid().ToString();
|
||||||
|
await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||||
|
await client.PublishStringAsync(topic, "Hello_World", MqttQualityOfServiceLevel.AtLeastOnce).ConfigureAwait(false);
|
||||||
|
|
||||||
|
SpinWait.SpinUntil(() => receivedMessage != null, 5000);
|
||||||
|
|
||||||
|
if (receivedMessage?.Topic != topic || receivedMessage?.ConvertPayloadToString() != "Hello_World")
|
||||||
|
{
|
||||||
|
throw new Exception("Message invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.UnsubscribeAsync(topic).ConfigureAwait(false);
|
||||||
|
await client.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
Write("[OK]\n", ConsoleColor.Green);
|
Write("[OK]\n", ConsoleColor.Green);
|
||||||
}
|
}
|
||||||
@@ -174,6 +208,5 @@ namespace MQTTnet.TestApp
|
|||||||
Console.ForegroundColor = color;
|
Console.ForegroundColor = color;
|
||||||
Console.Write(message);
|
Console.Write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,6 +80,8 @@ 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();
|
||||||
|
|
||||||
@@ -116,6 +118,8 @@ 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())
|
||||||
@@ -123,7 +127,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);
|
||||||
@@ -148,7 +152,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsFalse(lowLevelClient.IsConnected);
|
Assert.IsFalse(lowLevelClient.IsConnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ 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;
|
||||||
@@ -21,6 +22,67 @@ namespace MQTTnet.Tests.Client
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
public sealed class ManagedMqttClient_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Receive_While_Not_Cleanly_Disconnected()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||||
|
.WithCleanSession(false)
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send test data.
|
||||||
|
await senderClient.PublishStringAsync("topic1");
|
||||||
|
await LongTestDelay();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||||
|
await managedClient.StopAsync(false);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send new messages in the meantime.
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
@@ -33,7 +95,6 @@ namespace MQTTnet.Tests.Client
|
|||||||
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;
|
||||||
@@ -270,13 +331,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
await testEnvironment.StartServer().ConfigureAwait(false);
|
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||||
await sendingClient.PublishAsync(
|
await sendingClient.PublishStringAsync("topic", "A", retain: true);
|
||||||
new MqttApplicationMessage
|
|
||||||
{
|
|
||||||
Topic = "topic",
|
|
||||||
Payload = new byte[] { 1 },
|
|
||||||
Retain = true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait a bit for the retained message to be available
|
// Wait a bit for the retained message to be available
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
@@ -298,18 +353,15 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
await managedClient.SubscribeAsync("topic");
|
await managedClient.SubscribeAsync("topic");
|
||||||
|
|
||||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedManagedMessages.Count);
|
Assert.AreEqual(1, receivedManagedMessages.Count);
|
||||||
|
|
||||||
await managedClient.StopAsync();
|
await managedClient.StopAsync();
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
|
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(0.5)).Build());
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
// After reconnect and then some delay, the retained message must not be received,
|
// After reconnect and then some delay, the retained message must not be received,
|
||||||
@@ -318,7 +370,6 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
// Make sure that it gets received after subscribing again.
|
// Make sure that it gets received after subscribing again.
|
||||||
await managedClient.SubscribeAsync("topic");
|
await managedClient.SubscribeAsync("topic");
|
||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(2, receivedManagedMessages.Count);
|
Assert.AreEqual(2, receivedManagedMessages.Count);
|
||||||
|
|||||||
@@ -6,68 +6,93 @@ using System;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Tests.Mockups;
|
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Protocol;
|
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests.Extensions
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class RPC_Tests : BaseTestClass
|
public sealed class Rpc_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
var responseSender = await testEnvironment.ConnectClient();
|
||||||
|
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||||
|
{
|
||||||
|
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||||
|
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||||
|
|
||||||
|
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0()
|
public Task Execute_Success_With_QoS_0()
|
||||||
{
|
{
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
|
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V311);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_1()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_2()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0_MQTT_V5()
|
public Task Execute_Success_With_QoS_0_MQTT_V5()
|
||||||
{
|
{
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
|
return Execute_Success(MqttQualityOfServiceLevel.AtMostOnce, MqttProtocolVersion.V500);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
|
||||||
{
|
|
||||||
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_0_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
|
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_1()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V311);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_1_MQTT_V5()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.AtLeastOnce, MqttProtocolVersion.V500);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_1_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
|
return Execute_Success_MQTT_V5(MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_2()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V311);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public Task Execute_Success_With_QoS_2_MQTT_V5()
|
||||||
|
{
|
||||||
|
return Execute_Success(MqttQualityOfServiceLevel.ExactlyOnce, MqttProtocolVersion.V500);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
|
public Task Execute_Success_With_QoS_2_MQTT_V5_Use_ResponseTopic()
|
||||||
{
|
{
|
||||||
@@ -90,6 +115,31 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||||
|
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = new TestEnvironment(TestContext))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
var responseSender = await testEnvironment.ConnectClient();
|
||||||
|
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping");
|
||||||
|
|
||||||
|
responseSender.ApplicationMessageReceivedAsync += async e =>
|
||||||
|
{
|
||||||
|
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
||||||
|
await CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
||||||
|
|
||||||
|
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
||||||
|
{
|
||||||
|
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
||||||
public async Task Execute_With_Custom_Topic_Names()
|
public async Task Execute_With_Custom_Topic_Names()
|
||||||
@@ -98,14 +148,24 @@ namespace MQTTnet.Tests
|
|||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient();
|
|
||||||
|
|
||||||
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
|
var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()).Build());
|
||||||
|
|
||||||
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_Factory()
|
||||||
|
{
|
||||||
|
var factory = new MqttFactory();
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var rpcClient = factory.CreateMqttRpcClient(client);
|
||||||
|
|
||||||
|
Assert.IsNotNull(rpcClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
|
async Task Execute_Success(MqttQualityOfServiceLevel qosLevel, MqttProtocolVersion protocolVersion)
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
@@ -151,57 +211,6 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Execute_Success_MQTT_V5_Mixed_Clients()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
var responseSender = await testEnvironment.ConnectClient();
|
|
||||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
|
||||||
{
|
|
||||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
|
||||||
await responseSender.PublishStringAsync(e.ApplicationMessage.Topic + "/response", "pong");
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
|
||||||
|
|
||||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
|
||||||
{
|
|
||||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(5), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
Assert.AreEqual("pong", Encoding.UTF8.GetString(response));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
|
|
||||||
public async Task Execute_Timeout_MQTT_V5_Mixed_Clients()
|
|
||||||
{
|
|
||||||
using (var testEnvironment = new TestEnvironment(TestContext))
|
|
||||||
{
|
|
||||||
await testEnvironment.StartServer();
|
|
||||||
var responseSender = await testEnvironment.ConnectClient();
|
|
||||||
await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
|
|
||||||
responseSender.ApplicationMessageReceivedAsync += async e =>
|
|
||||||
{
|
|
||||||
Assert.IsNull(e.ApplicationMessage.ResponseTopic);
|
|
||||||
await CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
|
|
||||||
|
|
||||||
using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()))
|
|
||||||
{
|
|
||||||
var response = await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
|
class TestTopicStrategy : IMqttRpcClientTopicGenerationStrategy
|
||||||
{
|
{
|
||||||
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
public MqttRpcTopicPair CreateRpcTopics(TopicGenerationContext context)
|
||||||
@@ -214,4 +223,4 @@ namespace MQTTnet.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Extensions.WebSocket4Net;
|
||||||
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Extensions
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class WebSocket4Net_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttCommunicationException))]
|
||||||
|
public async Task Connect_Failed_With_Invalid_Server()
|
||||||
|
{
|
||||||
|
var factory = new MqttFactory().UseWebSocket4Net();
|
||||||
|
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var options = new MqttClientOptionsBuilder().WithWebSocketServer("ws://a.b/mqtt").WithTimeout(TimeSpan.FromSeconds(2)).Build();
|
||||||
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_Correct_Adapter()
|
||||||
|
{
|
||||||
|
// Should not throw exception
|
||||||
|
var factory = new MqttFactory().UseWebSocket4Net();
|
||||||
|
using (var client = factory.CreateMqttClient())
|
||||||
|
{
|
||||||
|
var adapterFactory = client.GetFieldValue("_adapterFactory");
|
||||||
|
|
||||||
|
Assert.IsInstanceOfType(adapterFactory, typeof(WebSocket4NetMqttClientAdapterFactory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Use_WebSocket4Net()
|
||||||
|
{
|
||||||
|
// Should not throw exception
|
||||||
|
new MqttFactory().UseWebSocket4Net();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Formatter
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttBufferReader_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||||
|
public void Fire_Exception_If_Not_Enough_Data()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
reader.SetBuffer(buffer, 0, 3);
|
||||||
|
|
||||||
|
// 1 byte is missing.
|
||||||
|
reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttProtocolViolationException), "Expected at least 4 bytes but there are only 3 bytes")]
|
||||||
|
public void Fire_Exception_If_Not_Enough_Data_With_Longer_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
reader.SetBuffer(buffer, 0, 3);
|
||||||
|
|
||||||
|
// 1 byte is missing.
|
||||||
|
reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Is_EndOfStream_Without_Buffer()
|
||||||
|
{
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Read_Remaining_Data_From_Larger_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
// This should only read 5 bytes even if more data is in the buffer
|
||||||
|
// due to custom bounds.
|
||||||
|
var remainingData = reader.ReadRemainingData();
|
||||||
|
|
||||||
|
Assert.IsTrue(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft);
|
||||||
|
Assert.AreEqual(5, remainingData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Read_Various_Positions_and_Offsets()
|
||||||
|
{
|
||||||
|
const int NumBufferElements = 1000;
|
||||||
|
const int NumTestSequences = 10000;
|
||||||
|
|
||||||
|
const string TestString = "The quick brown fox jumps over the lazy dog.";
|
||||||
|
|
||||||
|
var rndSeed = Environment.TickCount;
|
||||||
|
// rndSeed = ...; // assign random seed here if there is an error so that it can be repeated
|
||||||
|
var rnd = new Random(rndSeed);
|
||||||
|
// rndSeed is printed when an assert fails
|
||||||
|
|
||||||
|
var bufferElements = new List<ElementReference>();
|
||||||
|
|
||||||
|
byte[] elementBuffer;
|
||||||
|
|
||||||
|
// Fill a buffer with strings or numbers in big endian format
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var bw = new BinaryWriter(ms))
|
||||||
|
{
|
||||||
|
var bufferOffset = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < NumBufferElements; i++)
|
||||||
|
{
|
||||||
|
var nextElementType = (ElementReference.BufferElementType)rnd.Next(ElementReference.NumBufferElementTypes);
|
||||||
|
|
||||||
|
byte[] elementBytes = null;
|
||||||
|
uint elementNumberValue = 0; // all element number values fit into uint
|
||||||
|
string elementStringValue = null;
|
||||||
|
var elementSize = 0;
|
||||||
|
|
||||||
|
var alreadyBigEndian = false;
|
||||||
|
switch (nextElementType)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
var byteValue = (byte)i;
|
||||||
|
elementNumberValue = byteValue;
|
||||||
|
elementSize = sizeof(byte);
|
||||||
|
elementBytes = new byte[1] { byteValue };
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
var ushortValue = (ushort)i;
|
||||||
|
elementNumberValue = ushortValue;
|
||||||
|
elementSize = sizeof(ushort);
|
||||||
|
elementBytes = BitConverter.GetBytes(ushortValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
var uintValue = (uint)i;
|
||||||
|
elementNumberValue = uintValue;
|
||||||
|
elementSize = sizeof(uint);
|
||||||
|
elementBytes = BitConverter.GetBytes(uintValue);
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = (uint)i;
|
||||||
|
var writer = new MqttBufferWriter(4, 4);
|
||||||
|
writer.WriteVariableByteInteger(elementNumberValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
var stringLen = rnd.Next(TestString.Length);
|
||||||
|
elementStringValue = TestString.Substring(0, stringLen); // could be empty
|
||||||
|
var writer = new MqttBufferWriter(stringLen + 1, stringLen + 1);
|
||||||
|
writer.WriteString(elementStringValue);
|
||||||
|
elementSize = writer.Length;
|
||||||
|
elementBytes = new byte[elementSize];
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
Array.Copy(buffer, elementBytes, elementSize);
|
||||||
|
alreadyBigEndian = true; // nothing to swap
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian && !alreadyBigEndian)
|
||||||
|
{
|
||||||
|
Array.Reverse(elementBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
bw.Write(elementBytes);
|
||||||
|
bufferElements.Add(new ElementReference(nextElementType, elementSize, elementNumberValue, elementStringValue, bufferOffset));
|
||||||
|
bufferOffset += elementSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
elementBuffer = ms.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < NumTestSequences; i++)
|
||||||
|
{
|
||||||
|
var firstElementIndex = rnd.Next(NumBufferElements - 1);
|
||||||
|
// segmentElementIndex will be < NumBufferElements - 1, which means there is at least one byte left for segment element count
|
||||||
|
var elementCount = rnd.Next(NumBufferElements - firstElementIndex) + 1;
|
||||||
|
var lastElementIndex = firstElementIndex + elementCount - 1;
|
||||||
|
// Use element index to get buffer offsets
|
||||||
|
var lastElement = bufferElements[lastElementIndex];
|
||||||
|
var segmentStartPosition = bufferElements[firstElementIndex].BufferOffset;
|
||||||
|
var segmentEndPosition = lastElement.BufferOffset + lastElement.Size;
|
||||||
|
var segmentLength = segmentEndPosition - segmentStartPosition;
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(elementBuffer, segmentStartPosition, segmentLength);
|
||||||
|
|
||||||
|
// read all elements in the buffer segment; values should be as expected
|
||||||
|
for (var n = 0; n < elementCount; n++)
|
||||||
|
{
|
||||||
|
var element = bufferElements[firstElementIndex + n];
|
||||||
|
uint elementNumberValue = 0;
|
||||||
|
string elementStringValue = null;
|
||||||
|
|
||||||
|
var expectedPosition = element.BufferOffset - segmentStartPosition;
|
||||||
|
Assert.AreEqual(expectedPosition, reader.Position);
|
||||||
|
|
||||||
|
switch (element.Type)
|
||||||
|
{
|
||||||
|
case ElementReference.BufferElementType.Byte:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadByte();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.TwoByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadTwoByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.FourByteInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadFourByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.VariableSizeInt:
|
||||||
|
{
|
||||||
|
elementNumberValue = reader.ReadVariableByteInteger();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ElementReference.BufferElementType.String:
|
||||||
|
{
|
||||||
|
elementStringValue = reader.ReadString();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elementStringValue != null)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementStringValue, element.StringValue, $"'{elementStringValue}' not equal '{element.StringValue}' with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(elementNumberValue, element.NumberValue, $"{elementNumberValue} not equal {element.NumberValue} with random seed {rndSeed}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm end of stream
|
||||||
|
Assert.AreEqual(0, reader.BytesLeft, "BytesLeft not zero as expected with random seed " + rndSeed);
|
||||||
|
Assert.IsTrue(reader.EndOfStream, "Not end of stream as expected with random seed " + rndSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Full_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Partial_End_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 5, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Report_Correct_Length_For_Partial_Start_Buffer()
|
||||||
|
{
|
||||||
|
var buffer = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
|
||||||
|
// The used buffer contains more data than used!
|
||||||
|
reader.SetBuffer(buffer, 0, 5);
|
||||||
|
|
||||||
|
Assert.IsFalse(reader.EndOfStream);
|
||||||
|
Assert.AreEqual(5, reader.BytesLeft);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper class to build up a reference to elements of various types in a buffer
|
||||||
|
class ElementReference
|
||||||
|
{
|
||||||
|
public enum BufferElementType
|
||||||
|
{
|
||||||
|
Byte,
|
||||||
|
TwoByteInt,
|
||||||
|
FourByteInt,
|
||||||
|
VariableSizeInt,
|
||||||
|
String
|
||||||
|
}
|
||||||
|
|
||||||
|
public const int NumBufferElementTypes = 5;
|
||||||
|
|
||||||
|
public ElementReference(BufferElementType type, int size, uint numberValue, string stringValue, int bufferOffset)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Size = size;
|
||||||
|
NumberValue = numberValue;
|
||||||
|
StringValue = stringValue;
|
||||||
|
BufferOffset = bufferOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int BufferOffset { get; }
|
||||||
|
public uint NumberValue { get; }
|
||||||
|
public int Size { get; }
|
||||||
|
public string StringValue { get; }
|
||||||
|
public BufferElementType Type { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,13 +13,12 @@ using MQTTnet.Adapter;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Internal;
|
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Tests.Extensions;
|
using MQTTnet.Tests.Helpers;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests.Formatter
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttPacketSerialization_V3_Binary_Tests
|
public sealed class MqttPacketSerialization_V3_Binary_Tests
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests.Formatter
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttPacketSerialization_V3_Tests
|
public sealed class MqttPacketSerialization_V3_Tests
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using MQTTnet.Formatter;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests.Formatter
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttPacketSerialization_V5_Tests
|
public sealed class MqttPacketSerialization_V5_Tests
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Tests.Mockups;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Helpers
|
||||||
|
{
|
||||||
|
public static class MqttClientExtensions
|
||||||
|
{
|
||||||
|
public static TestApplicationMessageReceivedHandler TrackReceivedMessages(this IMqttClient client)
|
||||||
|
{
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestApplicationMessageReceivedHandler(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Extensions
|
namespace MQTTnet.Tests.Helpers
|
||||||
{
|
{
|
||||||
public static class MqttPacketWriterExtensions
|
public static class MqttPacketWriterExtensions
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Helpers
|
||||||
|
{
|
||||||
|
public static class ReflectionExtensions
|
||||||
|
{
|
||||||
|
public static object GetFieldValue(this object source, string fieldName)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = source.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Field {fieldName} not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.GetValue(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Internal
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class AsyncSignal_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(TaskCanceledException))]
|
||||||
|
public async Task Cancel_If_No_Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||||
|
{
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
Assert.Fail("There is no signal. So we must fail here!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ObjectDisposedException))]
|
||||||
|
public async Task Dispose_Properly()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// The timeout will not be reached but another task will kill the async signal.
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(999)))
|
||||||
|
{
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, CancellationToken.None);
|
||||||
|
asyncSignal.Dispose();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
Assert.Fail("There is no signal. So we must fail here!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Reset_Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// WaitAsync should fail because no signal is available.
|
||||||
|
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();
|
||||||
|
|
||||||
|
// WaitAsync should return directly because the signal is available.
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// The timeout will not be reached but another task will kill the async signal.
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, CancellationToken.None);
|
||||||
|
asyncSignal.Set();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
Assert.IsTrue(stopwatch.ElapsedMilliseconds > 900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public async Task Fail_For_Two_Waiters()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// This thread will wait properly because it is the first waiter.
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
// Now the current thread must fail because there is already a waiter.
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.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,69 +13,26 @@ namespace MQTTnet.Tests.Internal
|
|||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttPacketBus_Tests
|
public sealed class MqttPacketBus_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(OperationCanceledException))]
|
|
||||||
public async Task Wait_With_Empty_Bus()
|
|
||||||
{
|
|
||||||
var bus = new MqttPacketBus();
|
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
|
||||||
{
|
|
||||||
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]
|
[TestMethod]
|
||||||
public void Alternate_Priorities()
|
public void Alternate_Priorities()
|
||||||
{
|
{
|
||||||
var bus = new MqttPacketBus();
|
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 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 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);
|
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.AreEqual(9, bus.TotalItemsCount);
|
||||||
|
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
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(MqttSubAckPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
||||||
@@ -85,33 +42,10 @@ namespace MQTTnet.Tests.Internal
|
|||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
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(MqttSubAckPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
||||||
|
|
||||||
Assert.AreEqual(0, bus.TotalItemsCount);
|
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]
|
[TestMethod]
|
||||||
public void Await_Single_Packet()
|
public void Await_Single_Packet()
|
||||||
{
|
{
|
||||||
@@ -121,31 +55,97 @@ namespace MQTTnet.Tests.Internal
|
|||||||
|
|
||||||
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
|
|
||||||
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
item3.Delivered += (_, __) =>
|
item3.Completed += (_, __) =>
|
||||||
{
|
{
|
||||||
delivered = true;
|
delivered = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
// The third packet has the event attached.
|
// The third packet has the event attached.
|
||||||
Assert.IsTrue(delivered);
|
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]
|
||||||
|
[ExpectedException(typeof(TaskCanceledException))]
|
||||||
|
public async Task Wait_With_Empty_Bus()
|
||||||
|
{
|
||||||
|
var bus = new MqttPacketBus();
|
||||||
|
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
||||||
|
{
|
||||||
|
await bus.DequeueItemAsync(timeout.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,21 +7,20 @@
|
|||||||
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
<EnableNETAnalyzers>false</EnableNETAnalyzers>
|
||||||
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
|
||||||
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
<DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
|
||||||
|
<NoWarn>1591;NETSDK1138</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="4.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Net.Compilers" Version="4.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.ManagedClient\MQTTnet.Extensions.ManagedClient.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet.Extensions.Rpc\MQTTnet.Extensions.Rpc.csproj" />
|
||||||
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
<ProjectReference Include="..\..\Source\MQTTnet\MQTTnet.csproj" />
|
||||||
|
<ProjectReference Include="..\MQTTnet.Extensions.WebSocket4Net\MQTTnet.Extensions.WebSocket4Net.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -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.Success, result.ReasonCode);
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, 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.Success, result.ReasonCode);
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
c2.Dispose(); // Dispose will not send a DISCONNECT packet first so the will message must be sent.
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
Assert.AreEqual(1, receivedMessagesCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.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;
|
||||||
@@ -17,7 +19,23 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
|
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
|
||||||
{
|
{
|
||||||
mqttClient.ApplicationMessageReceivedAsync += MqttClientOnApplicationMessageReceivedAsync;
|
if (mqttClient == null)
|
||||||
|
{
|
||||||
|
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
|
||||||
@@ -39,7 +57,25 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task MqttClientOnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
public string GeneratePayloadSequence()
|
||||||
|
{
|
||||||
|
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,6 +11,7 @@ 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;
|
||||||
@@ -109,7 +110,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)
|
||||||
@@ -137,7 +138,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);
|
||||||
@@ -161,7 +162,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);
|
||||||
@@ -199,6 +200,16 @@ 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)
|
||||||
@@ -325,7 +336,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;
|
||||||
@@ -344,7 +355,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;
|
||||||
|
|||||||
@@ -8,27 +8,48 @@ using MQTTnet.Formatter;
|
|||||||
namespace MQTTnet.Tests
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class Protocol_Tests
|
public sealed class Protocol_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Encode_Four_Byte_Integer()
|
public void Encode_Four_Byte_Integer()
|
||||||
{
|
{
|
||||||
var writer = new MqttBufferWriter(4, 4);
|
var writer = new MqttBufferWriter(4, 4);
|
||||||
|
|
||||||
for (uint value = 0; value < 268435455; value++)
|
for (uint value = 0; value < 268435455; value++)
|
||||||
{
|
{
|
||||||
writer.WriteVariableByteInteger(value);
|
writer.WriteVariableByteInteger(value);
|
||||||
|
|
||||||
var buffer = writer.GetBuffer();
|
var buffer = writer.GetBuffer();
|
||||||
|
|
||||||
var reader = new MqttBufferReader();
|
var reader = new MqttBufferReader();
|
||||||
reader.SetBuffer(buffer, 0, writer.Length);
|
reader.SetBuffer(buffer, 0, writer.Length);
|
||||||
var checkValue = reader.ReadVariableByteInteger();
|
var checkValue = reader.ReadVariableByteInteger();
|
||||||
|
|
||||||
Assert.AreEqual(value, checkValue);
|
Assert.AreEqual(value, checkValue);
|
||||||
|
|
||||||
|
writer.Reset(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Encode_Two_Byte_Integer()
|
||||||
|
{
|
||||||
|
var writer = new MqttBufferWriter(2, 2);
|
||||||
|
|
||||||
|
for (ushort value = 0; value < ushort.MaxValue; value++)
|
||||||
|
{
|
||||||
|
writer.WriteTwoByteInteger(value);
|
||||||
|
|
||||||
|
var buffer = writer.GetBuffer();
|
||||||
|
|
||||||
|
var reader = new MqttBufferReader();
|
||||||
|
reader.SetBuffer(buffer, 0, writer.Length);
|
||||||
|
var checkValue = reader.ReadTwoByteInteger();
|
||||||
|
|
||||||
|
Assert.AreEqual(value, checkValue);
|
||||||
|
|
||||||
writer.Reset(0);
|
writer.Reset(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
@@ -60,5 +61,28 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Injected_Application_Message()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
MqttApplicationMessage interceptedMessage = null;
|
||||||
|
server.InterceptingPublishAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
interceptedMessage = eventArgs.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
|
||||||
|
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
Assert.IsNotNull(interceptedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(60));
|
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(120));
|
||||||
|
|
||||||
Assert.AreEqual(100000, receivedMessages);
|
Assert.AreEqual(100000, receivedMessages);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,104 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard1()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/+/room");
|
||||||
|
await SubscribeToTopic("house/+/room/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
CheckIsSubscribed("house/2/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||||
|
CheckIsNotSubscribed("house/1/study/bed");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard2()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/+/room");
|
||||||
|
await SubscribeToTopic("house/+/room/#");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room");
|
||||||
|
CheckIsSubscribed("house/2/room/bed");
|
||||||
|
CheckIsSubscribed("house/2/room/bed/cover");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/level");
|
||||||
|
CheckIsNotSubscribed("house/1/level/door");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard3()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/room");
|
||||||
|
await SubscribeToTopic("house/1/room/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room");
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/2/room");
|
||||||
|
CheckIsNotSubscribed("house/2/room/bed/cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard4()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/+/+");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/1/room");
|
||||||
|
CheckIsNotSubscribed("house/1/room/bed/cover");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task MqttSubscriptionsManager_SubscribeWildcard5()
|
||||||
|
{
|
||||||
|
await SubscribeToTopic("house/1/+/#");
|
||||||
|
|
||||||
|
CheckIsSubscribed("house/1/room/bed");
|
||||||
|
CheckIsSubscribed("house/1/room/chair");
|
||||||
|
CheckIsSubscribed("house/1/room/chair/leg");
|
||||||
|
CheckIsSubscribed("house/1/level/window");
|
||||||
|
CheckIsSubscribed("house/1/level/door");
|
||||||
|
|
||||||
|
CheckIsNotSubscribed("house/2/room/bed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task SubscribeToTopic(string topic)
|
||||||
|
{
|
||||||
|
var sp = new MqttSubscribePacket
|
||||||
|
{
|
||||||
|
TopicFilters = new List<MqttTopicFilter>
|
||||||
|
{
|
||||||
|
new MqttTopicFilter { Topic = topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIsSubscribed(string topic)
|
||||||
|
{
|
||||||
|
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||||
|
Assert.IsTrue(result.IsSubscribed);
|
||||||
|
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckIsNotSubscribed(string topic)
|
||||||
|
{
|
||||||
|
var result = CheckSubscriptions(topic, MqttQualityOfServiceLevel.AtMostOnce, "");
|
||||||
|
Assert.IsFalse(result.IsSubscribed);
|
||||||
|
}
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
@@ -130,9 +228,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
|
||||||
{
|
{
|
||||||
ulong topicHashMask; // not needed
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out _, out _);
|
||||||
bool hasWildcard; // not needed
|
|
||||||
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out topicHashMask, out hasWildcard);
|
|
||||||
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Publishing_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
|
public async Task Disconnect_While_Publishing()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// The client will be disconnect directly after subscribing!
|
||||||
|
server.InterceptingPublishAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,60 @@ 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()
|
||||||
{
|
{
|
||||||
@@ -99,25 +153,16 @@ namespace MQTTnet.Tests.Server
|
|||||||
|
|
||||||
await LongTestDelay();
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(2, eventArgs.Count);
|
Assert.AreEqual(1, eventArgs.Count);
|
||||||
|
|
||||||
var firstEvent = eventArgs[0];
|
var firstEvent = eventArgs[0];
|
||||||
|
|
||||||
Assert.IsNotNull(firstEvent);
|
Assert.IsNotNull(firstEvent);
|
||||||
Assert.IsNotNull(firstEvent.PublishPacket);
|
Assert.IsNotNull(firstEvent.PublishPacket);
|
||||||
Assert.IsNotNull(firstEvent.AcknowledgePacket);
|
Assert.IsNotNull(firstEvent.AcknowledgePacket);
|
||||||
Assert.IsFalse(firstEvent.IsCompleted);
|
Assert.IsTrue(firstEvent.IsCompleted);
|
||||||
|
|
||||||
Assert.AreEqual("A", firstEvent.PublishPacket.Topic);
|
Assert.AreEqual("A", firstEvent.PublishPacket.Topic);
|
||||||
|
|
||||||
var secondEvent = eventArgs[1];
|
|
||||||
|
|
||||||
Assert.IsNotNull(secondEvent);
|
|
||||||
Assert.IsNotNull(secondEvent.PublishPacket);
|
|
||||||
Assert.IsNotNull(secondEvent.AcknowledgePacket);
|
|
||||||
Assert.IsTrue(secondEvent.IsCompleted);
|
|
||||||
|
|
||||||
Assert.AreEqual("A", secondEvent.PublishPacket.Topic);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,66 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Downgrade_QoS_Level()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
// Add the retained message with QoS 2!
|
||||||
|
await c1.PublishAsync(
|
||||||
|
new MqttApplicationMessageBuilder().WithTopic("retained")
|
||||||
|
.WithPayload(new byte[3])
|
||||||
|
.WithRetainFlag()
|
||||||
|
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
// The second client uses QoS 1 so a downgrade is required.
|
||||||
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
messageHandler.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task No_Upgrade_QoS_Level()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var c1 = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
// Add the retained message with QoS 1!
|
||||||
|
await c1.PublishAsync(
|
||||||
|
new MqttApplicationMessageBuilder().WithTopic("retained")
|
||||||
|
.WithPayload(new byte[3])
|
||||||
|
.WithRetainFlag()
|
||||||
|
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||||
|
.Build());
|
||||||
|
|
||||||
|
// The second client uses QoS 2 so an upgrade is expected but according to the MQTT spec this is not supported!
|
||||||
|
var c2 = await testEnvironment.ConnectClient();
|
||||||
|
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
|
||||||
|
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce });
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
messageHandler.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, messageHandler.ReceivedEventArgs.First().ApplicationMessage.QualityOfServiceLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Receive_No_Retained_Message_After_Subscribe()
|
public async Task Receive_No_Retained_Message_After_Subscribe()
|
||||||
{
|
{
|
||||||
@@ -124,7 +184,7 @@ namespace MQTTnet.Tests.Server
|
|||||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||||
.WithRetainFlag()
|
.WithRetainFlag()
|
||||||
.Build());
|
.Build());
|
||||||
|
|
||||||
await c1.DisconnectAsync();
|
await c1.DisconnectAsync();
|
||||||
|
|
||||||
// Subscribe using a new client.
|
// Subscribe using a new client.
|
||||||
|
|||||||
@@ -121,10 +121,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NET452
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
[DataRow(MqttProtocolVersion.V310)]
|
||||||
[DataRow(MqttProtocolVersion.V311)]
|
[DataRow(MqttProtocolVersion.V311)]
|
||||||
[DataRow(MqttProtocolVersion.V500)]
|
[DataRow(MqttProtocolVersion.V500)]
|
||||||
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
|
public async Task Handle_Parallel_Connection_Attempts(MqttProtocolVersion protocolVersion)
|
||||||
@@ -150,8 +149,6 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Try to connect 50 clients at the same time.
|
// Try to connect 50 clients at the same time.
|
||||||
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||||
|
|
||||||
await Task.Delay(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();
|
||||||
@@ -162,13 +159,12 @@ namespace MQTTnet.Tests.Server
|
|||||||
var sendClient = await testEnvironment.ConnectClient(option2);
|
var sendClient = await testEnvironment.ConnectClient(option2);
|
||||||
await sendClient.PublishStringAsync("aaa", "1");
|
await sendClient.PublishStringAsync("aaa", "1");
|
||||||
|
|
||||||
await Task.Delay(3000);
|
await LongTestDelay();
|
||||||
|
|
||||||
Assert.AreEqual(true, hasReceive);
|
Assert.AreEqual(true, hasReceive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
[DataTestMethod]
|
[DataTestMethod]
|
||||||
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
|
||||||
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
|
||||||
|
|||||||
@@ -13,12 +13,56 @@ using MQTTnet.Internal;
|
|||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
|
using MQTTnet.Tests.Helpers;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Subscribe_Tests : BaseTestClass
|
public sealed class Subscribe_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("A", "A", true)]
|
||||||
|
[DataRow("A", "B", false)]
|
||||||
|
[DataRow("A", "#", true)]
|
||||||
|
[DataRow("A", "+", true)]
|
||||||
|
[DataRow("A/B", "A/B", true)]
|
||||||
|
[DataRow("A/B", "A/+", true)]
|
||||||
|
[DataRow("A/B", "A/#", true)]
|
||||||
|
[DataRow("A/B/C", "A/B/C", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/C", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/+", true)]
|
||||||
|
[DataRow("A/B/C", "A/+/#", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/B/C/D", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/+/C/+", true)]
|
||||||
|
[DataRow("A/B/C/D", "A/+/C/#", true)]
|
||||||
|
[DataRow("A/B/C", "A/B/+", true)]
|
||||||
|
[DataRow("A/B1/B2/C", "A/+/C", false)]
|
||||||
|
public async Task Subscription_Roundtrip(string topic, string filter, bool shouldWork)
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
await receiver.SubscribeAsync(filter).ConfigureAwait(false);
|
||||||
|
var receivedMessages = receiver.TrackReceivedMessages();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
await sender.PublishStringAsync(topic, "PAYLOAD").ConfigureAwait(false);
|
||||||
|
|
||||||
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (shouldWork)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(1, receivedMessages.Count, message: "The filter should work!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(0, receivedMessages.Count, message: "The filter should not work!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Deny_Invalid_Topic()
|
public async Task Deny_Invalid_Topic()
|
||||||
{
|
{
|
||||||
@@ -46,6 +90,22 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
|
public async Task Disconnect_While_Subscribing()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// The client will be disconnect directly after subscribing!
|
||||||
|
server.ClientSubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
await client.SubscribeAsync("#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Enqueue_Message_After_Subscription()
|
public async Task Enqueue_Message_After_Subscription()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
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.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
using MQTTnet.Tests.Mockups;
|
using MQTTnet.Tests.Mockups;
|
||||||
@@ -13,49 +13,301 @@ using MQTTnet.Tests.Mockups;
|
|||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests.Server
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class Subscription_TopicHash_Tests
|
public sealed class SubscriptionTopicHashTests
|
||||||
{
|
{
|
||||||
enum TopicHashSelector { NoWildcard, SingleWildcard, MultiWildcard };
|
MqttSession _clientSession;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[TestMethod]
|
||||||
|
public void Check_Hash_Bucket_Depth()
|
||||||
|
{
|
||||||
|
const int numPublishers = 5000;
|
||||||
|
const int numTopicsPerPublisher = 10;
|
||||||
|
|
||||||
|
TopicGenerator.Generate(
|
||||||
|
numPublishers,
|
||||||
|
numTopicsPerPublisher,
|
||||||
|
out var topicsByPublisher,
|
||||||
|
out _,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
// There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ...
|
||||||
|
// Hash bucket depths should remain low.
|
||||||
|
var bucketDepths = new Dictionary<ulong, int>();
|
||||||
|
var maxBucketDepth = 0;
|
||||||
|
ulong maxBucketDepthHash = 0;
|
||||||
|
|
||||||
|
var topicsByHash = new Dictionary<ulong, List<string>>();
|
||||||
|
|
||||||
|
foreach (var t in topicsByPublisher)
|
||||||
|
{
|
||||||
|
var topics = t.Value;
|
||||||
|
foreach (var topic in topics)
|
||||||
|
{
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
|
|
||||||
|
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
||||||
|
++currentValue;
|
||||||
|
bucketDepths[topicHash] = currentValue;
|
||||||
|
|
||||||
|
if (currentValue > maxBucketDepth)
|
||||||
|
{
|
||||||
|
maxBucketDepth = currentValue;
|
||||||
|
maxBucketDepthHash = topicHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!topicsByHash.TryGetValue(topicHash, out var topicList))
|
||||||
|
{
|
||||||
|
topicList = new List<string>();
|
||||||
|
topicsByHash.Add(topicHash, topicList);
|
||||||
|
}
|
||||||
|
|
||||||
|
topicList.Add(topic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxDepthTopics = topicsByHash[maxBucketDepthHash];
|
||||||
|
|
||||||
|
Console.Write("Max bucket depth is " + maxBucketDepth);
|
||||||
|
|
||||||
|
// for the test case the bucket depth should be less than 100
|
||||||
|
Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth");
|
||||||
|
}
|
||||||
|
|
||||||
MQTTnet.Server.MqttSession _clientSession;
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Match_Hash_Test_SingleWildCard()
|
public void Check_Selected_Topic_Hashes()
|
||||||
{
|
{
|
||||||
var l0 = "pub0";
|
CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF);
|
||||||
var l1 = "topic1";
|
CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF);
|
||||||
var l2 = "+";
|
CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF);
|
||||||
var l3 = "prop1";
|
CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000);
|
||||||
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
|
CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test long topic name with last level being #
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard()
|
||||||
|
{
|
||||||
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
/// <summary>
|
||||||
UInt64 topicHashMask;
|
/// Test long topic name with last level being +
|
||||||
bool topicHasWildcard;
|
/// </summary>
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
[TestMethod]
|
||||||
|
public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard()
|
||||||
|
{
|
||||||
|
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test long topic name with last level being #
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void Match_Hash_Test_LongTopic_MultiWildcard()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
const int numLevels = 8;
|
||||||
|
var levelNames = new string[numLevels];
|
||||||
|
for (var i = 0; i < numLevels; ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
sb.Append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
string levelName;
|
||||||
|
if (i == numLevels - 1)
|
||||||
|
{
|
||||||
|
// last one is #
|
||||||
|
levelName = "#";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
levelName = "level" + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
levelNames[i] = levelName;
|
||||||
|
sb.Append(levelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic = sb.ToString();
|
||||||
|
|
||||||
|
// UInt64 is limited to 8 levels
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
var hashBytes = GetBytes(topicHash);
|
var hashBytes = GetBytes(topicHash);
|
||||||
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
|
// all bytes should contain checksum
|
||||||
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
|
var count = 0;
|
||||||
Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch");
|
foreach (var h in hashBytes)
|
||||||
Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch");
|
{
|
||||||
Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch");
|
if (count < 7)
|
||||||
Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch");
|
{
|
||||||
Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch");
|
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
||||||
Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch");
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(h, 0, "checksum mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
// The mask should have zeroes where the wildcard and ff at the end
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mask should have ff except for last level
|
||||||
var hashMaskBytes = GetBytes(topicHashMask);
|
var hashMaskBytes = GetBytes(topicHashMask);
|
||||||
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
|
count = 0;
|
||||||
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
|
foreach (var h in hashMaskBytes)
|
||||||
Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch");
|
{
|
||||||
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
|
if (count < 7)
|
||||||
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
|
{
|
||||||
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
|
Assert.AreEqual(h, 0xff, "mask mismatch");
|
||||||
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
|
}
|
||||||
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(h, 0, "last mask mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test long topic name exceeding 8 levels
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void Match_Hash_Test_LongTopic_NoWildCard()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
const int numLevels = 9;
|
||||||
|
var levelNames = new string[numLevels];
|
||||||
|
for (var i = 0; i < numLevels; ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
sb.Append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelName = "level" + i;
|
||||||
|
levelNames[i] = levelName;
|
||||||
|
sb.Append(levelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic = sb.ToString();
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
||||||
|
|
||||||
|
|
||||||
|
var hashBytes = GetBytes(topicHash);
|
||||||
|
// all bytes should contain checksum
|
||||||
|
var count = 0;
|
||||||
|
foreach (var h in hashBytes)
|
||||||
|
{
|
||||||
|
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mask should have ff
|
||||||
|
var hashMaskBytes = GetBytes(topicHashMask);
|
||||||
|
count = 0;
|
||||||
|
foreach (var h in hashMaskBytes)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(h, 0xff, "mask mismatch");
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test long topic name with last level being +
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void Match_Hash_Test_LongTopic_SingleWildCard()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
const int numLevels = 8;
|
||||||
|
var levelNames = new string[numLevels];
|
||||||
|
for (var i = 0; i < numLevels; ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
sb.Append("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
string levelName;
|
||||||
|
if (i == numLevels - 1)
|
||||||
|
{
|
||||||
|
// last one is +
|
||||||
|
levelName = "+";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
levelName = "level" + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
levelNames[i] = levelName;
|
||||||
|
sb.Append(levelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var topic = sb.ToString();
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
|
|
||||||
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
|
|
||||||
|
var hashBytes = GetBytes(topicHash);
|
||||||
|
// all bytes should contain checksum
|
||||||
|
var count = 0;
|
||||||
|
foreach (var h in hashBytes)
|
||||||
|
{
|
||||||
|
if (count < 7)
|
||||||
|
{
|
||||||
|
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// wildcard position
|
||||||
|
Assert.AreEqual(h, 0, "checksum mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mask should have ff
|
||||||
|
var hashMaskBytes = GetBytes(topicHashMask);
|
||||||
|
count = 0;
|
||||||
|
foreach (var h in hashMaskBytes)
|
||||||
|
{
|
||||||
|
if (count < 7)
|
||||||
|
{
|
||||||
|
Assert.AreEqual(h, 0xff, "mask mismatch");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert.AreEqual(h, 0, "last mask mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
++count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -65,13 +317,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l1 = "topic1";
|
var l1 = "topic1";
|
||||||
var l2 = "sub1";
|
var l2 = "sub1";
|
||||||
var l3 = "#";
|
var l3 = "#";
|
||||||
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
UInt64 topicHash;
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
|
|
||||||
@@ -104,13 +352,9 @@ namespace MQTTnet.Tests.Server
|
|||||||
var l1 = "topic1";
|
var l1 = "topic1";
|
||||||
var l2 = "sub1";
|
var l2 = "sub1";
|
||||||
var l3 = "prop1";
|
var l3 = "prop1";
|
||||||
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
UInt64 topicHash;
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
|
||||||
|
|
||||||
@@ -137,225 +381,13 @@ namespace MQTTnet.Tests.Server
|
|||||||
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
|
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test long topic name with last level being #
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Match_Hash_Test_LongTopic_MultiWildcard()
|
public async Task Match_Hash_Test_Search_MultiWildcard()
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard);
|
||||||
const int NumLevels = 8;
|
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard");
|
||||||
var levelNames = new string[NumLevels];
|
// Should match all topics
|
||||||
for (var i = 0; i < NumLevels; ++i)
|
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
sb.Append("/");
|
|
||||||
string levelName;
|
|
||||||
if (i == NumLevels - 1)
|
|
||||||
{
|
|
||||||
// last one is #
|
|
||||||
levelName = "#";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
levelName = "level" + i;
|
|
||||||
}
|
|
||||||
levelNames[i] = levelName;
|
|
||||||
sb.Append(levelName);
|
|
||||||
}
|
|
||||||
var topic = sb.ToString();
|
|
||||||
|
|
||||||
// UInt64 is limited to 8 levels
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
|
||||||
|
|
||||||
var hashBytes = GetBytes(topicHash);
|
|
||||||
// all bytes should contain checksum
|
|
||||||
int count = 0;
|
|
||||||
foreach (var h in hashBytes)
|
|
||||||
{
|
|
||||||
if (count < 7)
|
|
||||||
{
|
|
||||||
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0, "checksum mismatch");
|
|
||||||
}
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
// The mask should have ff except for last level
|
|
||||||
var hashMaskBytes = GetBytes(topicHashMask);
|
|
||||||
count = 0;
|
|
||||||
foreach (var h in hashMaskBytes)
|
|
||||||
{
|
|
||||||
if (count < 7)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0xff, "mask mismatch");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0, "last mask mismatch");
|
|
||||||
}
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test long topic name with last level being +
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void Match_Hash_Test_LongTopic_SingleWildCard()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
const int NumLevels = 8;
|
|
||||||
var levelNames = new string[NumLevels];
|
|
||||||
for (var i = 0; i < NumLevels; ++i)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
sb.Append("/");
|
|
||||||
string levelName;
|
|
||||||
if (i == NumLevels - 1)
|
|
||||||
{
|
|
||||||
// last one is +
|
|
||||||
levelName = "+";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
levelName = "level" + i;
|
|
||||||
}
|
|
||||||
levelNames[i] = levelName;
|
|
||||||
sb.Append(levelName);
|
|
||||||
}
|
|
||||||
var topic = sb.ToString();
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
|
||||||
|
|
||||||
|
|
||||||
var hashBytes = GetBytes(topicHash);
|
|
||||||
// all bytes should contain checksum
|
|
||||||
int count = 0;
|
|
||||||
foreach (var h in hashBytes)
|
|
||||||
{
|
|
||||||
if (count < 7)
|
|
||||||
{
|
|
||||||
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// wildcard position
|
|
||||||
Assert.AreEqual(h, 0, "checksum mismatch");
|
|
||||||
}
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
// The mask should have ff
|
|
||||||
var hashMaskBytes = GetBytes(topicHashMask);
|
|
||||||
count = 0;
|
|
||||||
foreach (var h in hashMaskBytes)
|
|
||||||
{
|
|
||||||
if (count < 7)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0xff, "mask mismatch");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0, "last mask mismatch");
|
|
||||||
}
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test long topic name exceeding 8 levels
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void Match_Hash_Test_LongTopic_NoWildCard()
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
const int NumLevels = 9;
|
|
||||||
var levelNames = new string[NumLevels];
|
|
||||||
for(var i=0; i < NumLevels; ++i)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
sb.Append("/");
|
|
||||||
var levelName = "level" + i;
|
|
||||||
levelNames[i] = levelName;
|
|
||||||
sb.Append(levelName);
|
|
||||||
}
|
|
||||||
var topic = sb.ToString();
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not present");
|
|
||||||
|
|
||||||
|
|
||||||
var hashBytes = GetBytes(topicHash);
|
|
||||||
// all bytes should contain checksum
|
|
||||||
int count = 0;
|
|
||||||
foreach(var h in hashBytes)
|
|
||||||
{
|
|
||||||
Assert.AreNotEqual(h, 0, "checksum mismatch");
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
// The mask should have ff
|
|
||||||
var hashMaskBytes = GetBytes(topicHashMask);
|
|
||||||
count = 0;
|
|
||||||
foreach (var h in hashMaskBytes)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(h, 0xff, "mask mismatch");
|
|
||||||
++count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test long topic name with last level being +
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void Match_Hash_Test_LongTopic_DetectSingleLevelWildcard()
|
|
||||||
{
|
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/+";
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test long topic name with last level being #
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void Match_Hash_Test_LongTopic_DetectMultiLevelWildcard()
|
|
||||||
{
|
|
||||||
var topic = "asdfasdf/asdfasdf/asdfasdf/asdfasdf/asdfas/dfaf/assfdgsdfgdf/#";
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 topicHashMask;
|
|
||||||
bool topicHasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
|
|
||||||
|
|
||||||
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@@ -407,134 +439,125 @@ namespace MQTTnet.Tests.Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Match_Hash_Test_Search_MultiWildcard()
|
public void Match_Hash_Test_SingleWildCard()
|
||||||
{
|
{
|
||||||
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard);
|
var l0 = "pub0";
|
||||||
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard");
|
var l1 = "topic1";
|
||||||
// Should match all topics
|
var l2 = "+";
|
||||||
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
|
var l3 = "prop1";
|
||||||
}
|
var topic = $"{l0}/{l1}/{l2}/{l3}";
|
||||||
|
|
||||||
/// <summary>
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var topicHashMask, out var topicHasWildcard);
|
||||||
/// Even fairly regularly named topics as generated by the topic generator should result in shallow hash buckets
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
[TestMethod]
|
|
||||||
public void Check_Hash_Bucket_Depth()
|
|
||||||
{
|
|
||||||
Dictionary<string, List<string>> topicsByPublisher;
|
|
||||||
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
|
|
||||||
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
|
|
||||||
|
|
||||||
const int NumPublishers = 5000;
|
Assert.IsTrue(topicHasWildcard, "Wildcard not detected");
|
||||||
const int NumTopicsPerPublisher = 10;
|
|
||||||
|
|
||||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
|
var hashBytes = GetBytes(topicHash);
|
||||||
|
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
|
||||||
|
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
|
||||||
|
Assert.AreEqual(hashBytes[2], 0, "checksum 2 mismatch");
|
||||||
|
Assert.AreNotEqual(hashBytes[3], 0, "checksum 3 mismatch");
|
||||||
|
Assert.AreEqual(hashBytes[4], 0, "checksum 4 mismatch");
|
||||||
|
Assert.AreEqual(hashBytes[5], 0, "checksum 5 mismatch");
|
||||||
|
Assert.AreEqual(hashBytes[6], 0, "checksum 6 mismatch");
|
||||||
|
Assert.AreEqual(hashBytes[7], 0, "checksum 7 mismatch");
|
||||||
|
|
||||||
// There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ...
|
// The mask should have zeroes where the wildcard and ff at the end
|
||||||
// Hash bucket depths should remain low.
|
var hashMaskBytes = GetBytes(topicHashMask);
|
||||||
var bucketDepths = new Dictionary<UInt64, int>();
|
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
|
||||||
int maxBucketDepth = 0;
|
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
|
||||||
UInt64 maxBucketDepthHash = 0;
|
Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch");
|
||||||
|
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
|
||||||
var topicsByHash = new Dictionary<UInt64, List<string>>();
|
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
|
||||||
|
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
|
||||||
foreach (var t in topicsByPublisher)
|
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
|
||||||
{
|
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
|
||||||
var topics = t.Value;
|
|
||||||
foreach (var topic in topics)
|
|
||||||
{
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 hashMask;
|
|
||||||
bool hasWildcard;
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out hashMask, out hasWildcard);
|
|
||||||
|
|
||||||
bucketDepths.TryGetValue(topicHash, out var currentValue);
|
|
||||||
++currentValue;
|
|
||||||
bucketDepths[topicHash] = currentValue;
|
|
||||||
|
|
||||||
if (currentValue > maxBucketDepth)
|
|
||||||
{
|
|
||||||
maxBucketDepth = currentValue;
|
|
||||||
maxBucketDepthHash = topicHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!topicsByHash.TryGetValue(topicHash, out var topicList))
|
|
||||||
{
|
|
||||||
topicList = new List<string>();
|
|
||||||
topicsByHash.Add(topicHash, topicList);
|
|
||||||
}
|
|
||||||
topicList.Add(topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxDepthTopics = topicsByHash[maxBucketDepthHash];
|
|
||||||
|
|
||||||
Console.Write("Max bucket depth is " + maxBucketDepth);
|
|
||||||
|
|
||||||
// for the test case the bucket depth should be less than 100
|
|
||||||
Assert.IsTrue(maxBucketDepth < 100, "Unexpected high topic hash bucket depth");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Check_Selected_Topic_Hashes()
|
|
||||||
{
|
|
||||||
CheckTopicHash("client1/building1/level1/sensor1", 0x655D4AF100000000, 0xFFFFFFFFFFFFFFFF);
|
|
||||||
CheckTopicHash("client1/building1/+/sensor1", 0x655D00F100000000, 0xFFFF00FFFFFFFFFF);
|
|
||||||
CheckTopicHash("client1/+/level1/+", 0x65004A0000000000, 0xFF00FF00FFFFFFFF);
|
|
||||||
CheckTopicHash("client1/building1/level1/#", 0x655D4A0000000000, 0xFFFFFF0000000000);
|
|
||||||
CheckTopicHash("client1/+/level1/#", 0x65004A0000000000, 0xFF00FF0000000000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
void CheckTopicHash(string topic, ulong expectedHash, ulong expectedHashMask)
|
||||||
{
|
{
|
||||||
ulong topicHash;
|
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out var hashMask, out var hasWildcard);
|
||||||
ulong hashMask;
|
|
||||||
bool hasWildcard;
|
|
||||||
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out hashMask, out hasWildcard);
|
|
||||||
|
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("Topic: " + topic);
|
Console.WriteLine("Topic: " + topic);
|
||||||
Console.WriteLine(string.Format("Hash: {0:X8}", topicHash));
|
Console.WriteLine($"Hash: {topicHash:X8}");
|
||||||
Console.WriteLine(string.Format("Hash Mask: {0:X8}", hashMask));
|
Console.WriteLine($"Hash Mask: {hashMask:X8}");
|
||||||
|
|
||||||
Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?");
|
Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?");
|
||||||
Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected");
|
Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CheckTopicSubscriptions(MqttSession clientSession, List<string> topicsToFind, string subject)
|
||||||
|
{
|
||||||
|
var matchCount = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
var resultCount = 0;
|
||||||
|
|
||||||
|
var stopWatch = new Stopwatch();
|
||||||
|
stopWatch.Start();
|
||||||
|
var countUp = 0;
|
||||||
|
var countDown = topicsToFind.Count - 1;
|
||||||
|
for (; countUp < topicsToFind.Count; ++countUp, --countDown)
|
||||||
|
{
|
||||||
|
var topicToFind = topicsToFind[countDown];
|
||||||
|
|
||||||
|
MqttSubscription.CalculateTopicHash(topicToFind, out var topicHash, out _, out _);
|
||||||
|
|
||||||
|
clientSession.TryCheckSubscriptions(topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient", out var result);
|
||||||
|
if (result.IsSubscribed)
|
||||||
|
{
|
||||||
|
++matchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
++resultCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
|
||||||
|
Console.Write("Match count: " + matchCount + "; ");
|
||||||
|
|
||||||
|
Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] GetBytes(ulong value)
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(value);
|
||||||
|
// Ensure that highest byte comes first for comparison left to right
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
return bytes.Reverse().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async Task<List<string>> PrepareTopicHashSubscriptions(TopicHashSelector selector)
|
async Task<List<string>> PrepareTopicHashSubscriptions(TopicHashSelector selector)
|
||||||
{
|
{
|
||||||
Dictionary<string, List<string>> topicsByPublisher;
|
const int numPublishers = 1;
|
||||||
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
|
const int numTopicsPerPublisher = 10000;
|
||||||
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
|
|
||||||
|
|
||||||
const int NumPublishers = 1;
|
TopicGenerator.Generate(
|
||||||
const int NumTopicsPerPublisher = 10000;
|
numPublishers,
|
||||||
|
numTopicsPerPublisher,
|
||||||
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
|
out var topicsByPublisher,
|
||||||
|
out var singleWildcardTopicsByPublisher,
|
||||||
|
out var multiWildcardTopicsByPublisher);
|
||||||
|
|
||||||
var topics = topicsByPublisher.FirstOrDefault().Value;
|
var topics = topicsByPublisher.FirstOrDefault().Value;
|
||||||
var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value;
|
var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value;
|
||||||
var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value;
|
var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value;
|
||||||
|
|
||||||
const string ClientId = "Client1";
|
const string clientId = "Client1";
|
||||||
var logger = new Mockups.TestLogger();
|
var logger = new TestLogger();
|
||||||
var serverOptions = new MQTTnet.Server.MqttServerOptions();
|
var serverOptions = new MqttServerOptions();
|
||||||
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
|
var eventContainer = new MqttServerEventContainer();
|
||||||
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
|
||||||
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
|
||||||
_clientSession = new MQTTnet.Server.MqttSession(
|
_clientSession = new MqttSession(clientId, false, new Dictionary<object, object>(), serverOptions, eventContainer, retainedMessagesManager, sessionManager);
|
||||||
ClientId,
|
|
||||||
false,
|
|
||||||
new Dictionary<object, object>(),
|
|
||||||
serverOptions,
|
|
||||||
eventContainer,
|
|
||||||
retainedMessagesManager,
|
|
||||||
sessionManager
|
|
||||||
);
|
|
||||||
|
|
||||||
List<string> topicsToSubscribe;
|
List<string> topicsToSubscribe;
|
||||||
|
|
||||||
@@ -550,63 +573,26 @@ namespace MQTTnet.Tests.Server
|
|||||||
topicsToSubscribe = topics;
|
topicsToSubscribe = topics;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var t in topicsToSubscribe)
|
foreach (var t in topicsToSubscribe)
|
||||||
{
|
{
|
||||||
var subPacket = new Packets.MqttSubscribePacket();
|
var subPacket = new MqttSubscribePacket();
|
||||||
var filter = new Packets.MqttTopicFilter();
|
var filter = new MqttTopicFilter
|
||||||
filter.Topic = t;
|
{
|
||||||
|
Topic = t
|
||||||
|
};
|
||||||
subPacket.TopicFilters.Add(filter);
|
subPacket.TopicFilters.Add(filter);
|
||||||
await _clientSession.SubscriptionsManager.Subscribe(subPacket, default(CancellationToken));
|
await _clientSession.Subscribe(subPacket, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
return topics;
|
return topics;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CheckTopicSubscriptions(MQTTnet.Server.MqttSession clientSession, List<string> topicsToFind, string subject)
|
enum TopicHashSelector
|
||||||
{
|
{
|
||||||
var matchCount = 0;
|
NoWildcard,
|
||||||
|
SingleWildcard,
|
||||||
{
|
MultiWildcard
|
||||||
int resultCount = 0;
|
|
||||||
|
|
||||||
var stopWatch = new System.Diagnostics.Stopwatch();
|
|
||||||
stopWatch.Start();
|
|
||||||
var countUp = 0;
|
|
||||||
var countDown = topicsToFind.Count - 1;
|
|
||||||
for (; countUp < topicsToFind.Count; ++countUp, --countDown)
|
|
||||||
{
|
|
||||||
var topicToFind = topicsToFind[countDown];
|
|
||||||
|
|
||||||
UInt64 topicHash;
|
|
||||||
UInt64 hashMask;
|
|
||||||
bool hasWildcard;
|
|
||||||
MQTTnet.Server.MqttSubscription.CalculateTopicHash((string)topicToFind, out topicHash, out hashMask, out hasWildcard);
|
|
||||||
|
|
||||||
var result = clientSession.SubscriptionsManager.CheckSubscriptions((string)topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient");
|
|
||||||
if (result.IsSubscribed)
|
|
||||||
{
|
|
||||||
++matchCount;
|
|
||||||
}
|
|
||||||
++resultCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopWatch.Stop();
|
|
||||||
|
|
||||||
Console.Write("Match count: " + matchCount + "; ");
|
|
||||||
|
|
||||||
Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] GetBytes(UInt64 value)
|
|
||||||
{
|
|
||||||
var bytes = BitConverter.GetBytes(value);
|
|
||||||
// Ensure that highest byte comes first for comparison left to right
|
|
||||||
if (BitConverter.IsLittleEndian)
|
|
||||||
return bytes.Reverse().ToArray();
|
|
||||||
return bytes;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// 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.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Unsubscribe_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
|
public async Task Disconnect_While_Unsubscribing()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// The client will be disconnect directly after subscribing!
|
||||||
|
server.ClientUnsubscribedTopicAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
await client.SubscribeAsync("#");
|
||||||
|
await client.UnsubscribeAsync("#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
@@ -10,33 +9,51 @@ namespace MQTTnet.Tests.Server
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class Will_Tests : BaseTestClass
|
public sealed class Will_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Intercept_Will_Message()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer().ConfigureAwait(false);
|
||||||
|
|
||||||
|
MqttApplicationMessage willMessage = null;
|
||||||
|
server.InterceptingPublishAsync += eventArgs =>
|
||||||
|
{
|
||||||
|
willMessage = eventArgs.ApplicationMessage;
|
||||||
|
return CompletedTask.Instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||||
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
|
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
|
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
|
Assert.IsNotNull(willMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
|
||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
var receivedMessagesCount = 0;
|
|
||||||
|
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var receiver = await testEnvironment.ConnectClient().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will");
|
||||||
|
var sender = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
|
await sender.DisconnectAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient();
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
Assert.AreEqual(0, receivedMessages.ReceivedEventArgs.Count);
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
|
||||||
await c2.DisconnectAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
await Task.Delay(1000);
|
|
||||||
|
|
||||||
Assert.AreEqual(0, receivedMessagesCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,24 +64,17 @@ namespace MQTTnet.Tests.Server
|
|||||||
{
|
{
|
||||||
await testEnvironment.StartServer();
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
|
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()).ConfigureAwait(false);
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(receiver);
|
||||||
var receivedMessagesCount = 0;
|
await receiver.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
||||||
c1.ApplicationMessageReceivedAsync += e =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref receivedMessagesCount);
|
|
||||||
return CompletedTask.Instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
|
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithWillQualityOfServiceLevel(MqttQualityOfServiceLevel.AtMostOnce);
|
||||||
var c2 = await testEnvironment.ConnectClient(clientOptions);
|
var takeOverClient = await testEnvironment.ConnectClient(clientOptions).ConfigureAwait(false);
|
||||||
c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
takeOverClient.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent.
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await LongTestDelay().ConfigureAwait(false);
|
||||||
|
|
||||||
Assert.AreEqual(1, receivedMessagesCount);
|
Assert.AreEqual(1, receivedMessages.ReceivedEventArgs.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using MQTTnet.Channel;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
@@ -87,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 cancelled.", cancellationToken);
|
throw new OperationCanceledException("MQTT connect canceled.", cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that the exception from the connect task gets thrown.
|
// Make sure that the exception from the connect task gets thrown.
|
||||||
@@ -296,11 +295,29 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
while (totalBytesRead < buffer.Length)
|
while (totalBytesRead < buffer.Length)
|
||||||
{
|
{
|
||||||
var bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
// Check two times for cancellation because the call to _ReadAsync_ might block for some time.
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return ReadFixedHeaderResult.Cancelled;
|
return ReadFixedHeaderResult.Canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.Canceled;
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.ConnectionClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.Canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytesRead == 0)
|
if (bytesRead == 0)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
|
|
||||||
|
namespace MQTTnet.Client
|
||||||
|
{
|
||||||
|
public sealed class MqttClientDisconnectedException : MqttCommunicationException
|
||||||
|
{
|
||||||
|
public MqttClientDisconnectedException(Exception innerException) : base("The MQTT client is disconnected.", innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -3,14 +3,15 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Exceptions
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttUnexpectedDisconnectReceivedException : MqttCommunicationException
|
public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException
|
||||||
{
|
{
|
||||||
public MqttUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
|
public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
|
||||||
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
|
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
|
||||||
{
|
{
|
||||||
ReasonCode = disconnectPacket.ReasonCode;
|
ReasonCode = disconnectPacket.ReasonCode;
|
||||||
@@ -19,7 +19,6 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClient : Disposable, IMqttClient
|
public sealed class MqttClient : Disposable, IMqttClient
|
||||||
{
|
{
|
||||||
readonly IMqttClientAdapterFactory _adapterFactory;
|
readonly IMqttClientAdapterFactory _adapterFactory;
|
||||||
readonly MqttApplicationMessageFactory _applicationMessageFactory = new MqttApplicationMessageFactory();
|
|
||||||
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
readonly AsyncEvent<MqttApplicationMessageReceivedEventArgs> _applicationMessageReceivedEvent = new AsyncEvent<MqttApplicationMessageReceivedEventArgs>();
|
||||||
|
|
||||||
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
readonly MqttClientPublishResultFactory _clientPublishResultFactory = new MqttClientPublishResultFactory();
|
||||||
@@ -32,25 +31,20 @@ 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 MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
|
|
||||||
readonly MqttPacketFactories _packetFactories = new MqttPacketFactories();
|
|
||||||
|
|
||||||
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
|
||||||
readonly IMqttNetLogger _rootLogger;
|
readonly IMqttNetLogger _rootLogger;
|
||||||
|
|
||||||
IMqttChannelAdapter _adapter;
|
IMqttChannelAdapter _adapter;
|
||||||
|
|
||||||
CancellationTokenSource _backgroundCancellationTokenSource;
|
CancellationTokenSource _backgroundCancellationTokenSource;
|
||||||
bool _cleanDisconnectInitiated;
|
bool _cleanDisconnectInitiated;
|
||||||
volatile int _connectionStatus;
|
volatile int _connectionStatus;
|
||||||
|
|
||||||
MqttClientDisconnectReason _disconnectReason;
|
MqttClientDisconnectReason _disconnectReason;
|
||||||
string _disconnectReasonString;
|
string _disconnectReasonString;
|
||||||
Task _keepAlivePacketsSenderTask;
|
Task _keepAlivePacketsSenderTask;
|
||||||
|
|
||||||
DateTime _lastPacketSentTimestamp;
|
DateTime _lastPacketSentTimestamp;
|
||||||
|
MqttPacketDispatcher _packetDispatcher;
|
||||||
Task _packetReceiverTask;
|
Task _packetReceiverTask;
|
||||||
|
|
||||||
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
AsyncQueue<MqttPublishPacket> _publishPacketReceiverQueue;
|
||||||
Task _publishPacketReceiverTask;
|
Task _publishPacketReceiverTask;
|
||||||
|
|
||||||
@@ -128,7 +122,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
_packetIdentifierProvider.Reset();
|
_packetIdentifierProvider.Reset();
|
||||||
_packetDispatcher.CancelAll();
|
_packetDispatcher = new MqttPacketDispatcher();
|
||||||
|
|
||||||
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
_backgroundCancellationTokenSource = new CancellationTokenSource();
|
||||||
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;
|
||||||
@@ -211,7 +205,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
if (clientWasConnected)
|
if (clientWasConnected)
|
||||||
{
|
{
|
||||||
var disconnectPacket = _packetFactories.Disconnect.Create(options);
|
var disconnectPacket = MqttPacketFactories.Disconnect.Create(options);
|
||||||
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
await SendAsync(disconnectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +239,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var publishPacket = _packetFactories.Publish.Create(applicationMessage);
|
var publishPacket = MqttPacketFactories.Publish.Create(applicationMessage);
|
||||||
|
|
||||||
switch (applicationMessage.QualityOfServiceLevel)
|
switch (applicationMessage.QualityOfServiceLevel)
|
||||||
{
|
{
|
||||||
@@ -305,7 +299,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var subscribePacket = _packetFactories.Subscribe.Create(options);
|
var subscribePacket = MqttPacketFactories.Subscribe.Create(options);
|
||||||
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
subscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
var subAckPacket = await SendAndReceiveAsync<MqttSubAckPacket>(subscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -323,7 +317,7 @@ namespace MQTTnet.Client
|
|||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
ThrowIfNotConnected();
|
ThrowIfNotConnected();
|
||||||
|
|
||||||
var unsubscribePacket = _packetFactories.Unsubscribe.Create(options);
|
var unsubscribePacket = MqttPacketFactories.Unsubscribe.Create(options);
|
||||||
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
unsubscribePacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
|
||||||
|
|
||||||
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
var unsubAckPacket = await SendAndReceiveAsync<MqttUnsubAckPacket>(unsubscribePacket, cancellationToken).ConfigureAwait(false);
|
||||||
@@ -351,7 +345,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubAckPacket = _packetFactories.PubAck.Create(eventArgs);
|
var pubAckPacket = MqttPacketFactories.PubAck.Create(eventArgs);
|
||||||
return SendAsync(pubAckPacket, cancellationToken);
|
return SendAsync(pubAckPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,7 +353,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
if (!eventArgs.ProcessingFailed)
|
if (!eventArgs.ProcessingFailed)
|
||||||
{
|
{
|
||||||
var pubRecPacket = _packetFactories.PubRec.Create(eventArgs);
|
var pubRecPacket = MqttPacketFactories.PubRec.Create(eventArgs);
|
||||||
return SendAsync(pubRecPacket, cancellationToken);
|
return SendAsync(pubRecPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,7 +371,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var connectPacket = _packetFactories.Connect.Create(options);
|
var connectPacket = MqttPacketFactories.Connect.Create(options);
|
||||||
|
|
||||||
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
var connAckPacket = await SendAndReceiveAsync<MqttConnAckPacket>(connectPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -414,6 +408,10 @@ namespace MQTTnet.Client
|
|||||||
_publishPacketReceiverQueue = null;
|
_publishPacketReceiverQueue = null;
|
||||||
|
|
||||||
_adapter?.Dispose();
|
_adapter?.Dispose();
|
||||||
|
_adapter = null;
|
||||||
|
|
||||||
|
_packetDispatcher?.Dispose();
|
||||||
|
_packetDispatcher = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,6 +445,8 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_packetDispatcher.Dispose(new MqttClientDisconnectedException(exception));
|
||||||
|
|
||||||
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
var receiverTask = WaitForTaskAsync(_packetReceiverTask, sender);
|
||||||
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
var publishPacketReceiverTask = WaitForTaskAsync(_publishPacketReceiverTask, sender);
|
||||||
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
var keepAliveTask = WaitForTaskAsync(_keepAlivePacketsSenderTask, sender);
|
||||||
@@ -533,7 +533,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
async Task<MqttApplicationMessageReceivedEventArgs> HandleReceivedApplicationMessageAsync(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
var applicationMessage = _applicationMessageFactory.Create(publishPacket);
|
var applicationMessage = MqttApplicationMessageFactory.Create(publishPacket);
|
||||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
var eventArgs = new MqttApplicationMessageReceivedEventArgs(Options.ClientId, applicationMessage, publishPacket, AcknowledgeReceivedPublishPacket);
|
||||||
|
|
||||||
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
await _applicationMessageReceivedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
|
||||||
@@ -558,7 +558,7 @@ namespace MQTTnet.Client
|
|||||||
_disconnectReasonString = disconnectPacket.ReasonString;
|
_disconnectReasonString = disconnectPacket.ReasonString;
|
||||||
|
|
||||||
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
// Also dispatch disconnect to waiting threads to generate a proper exception.
|
||||||
_packetDispatcher.FailAll(new MqttUnexpectedDisconnectReceivedException(disconnectPacket));
|
_packetDispatcher.Dispose(new MqttClientUnexpectedDisconnectReceivedException(disconnectPacket));
|
||||||
|
|
||||||
return DisconnectInternalAsync(_packetReceiverTask, null, null);
|
return DisconnectInternalAsync(_packetReceiverTask, null, null);
|
||||||
}
|
}
|
||||||
@@ -599,7 +599,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
// The packet is unknown. Probably due to a restart of the client.
|
// The packet is unknown. Probably due to a restart of the client.
|
||||||
// So wen send this to the server to trigger a full resend of the message.
|
// So wen send this to the server to trigger a full resend of the message.
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.PacketIdentifierNotFound);
|
||||||
return SendAsync(pubRelPacket, cancellationToken);
|
return SendAsync(pubRelPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +608,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var pubCompPacket = _packetFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubCompPacket = MqttPacketFactories.PubComp.Create(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
return SendAsync(pubCompPacket, cancellationToken);
|
return SendAsync(pubCompPacket, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,7 +635,7 @@ namespace MQTTnet.Client
|
|||||||
|
|
||||||
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
var pubRecPacket = await SendAndReceiveAsync<MqttPubRecPacket>(publishPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
var pubRelPacket = MqttPacketFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
|
||||||
|
|
||||||
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
var pubCompPacket = await SendAndReceiveAsync<MqttPubCompPacket>(pubRelPacket, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -776,7 +776,7 @@ namespace MQTTnet.Client
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(exception, "Error while receiving packets.");
|
_logger.Error(exception, $"Error while processing received packet ({packet.GetType().Name}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
_packetDispatcher.FailAll(exception);
|
_packetDispatcher.FailAll(exception);
|
||||||
@@ -827,6 +827,11 @@ namespace MQTTnet.Client
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exception is AggregateException aggregateException)
|
||||||
|
{
|
||||||
|
exception = aggregateException.GetBaseException();
|
||||||
|
}
|
||||||
|
|
||||||
if (exception is OperationCanceledException)
|
if (exception is OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,26 +14,6 @@ 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)
|
||||||
@@ -89,6 +69,17 @@ 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)
|
||||||
@@ -137,6 +128,26 @@ 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,29 +10,38 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientPublishResult
|
public sealed class MqttClientPublishResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the packet identifier which was used for this publish.
|
/// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or
|
||||||
|
/// _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 packet.
|
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||||
/// 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.
|
/// packet.
|
||||||
/// The feature is very similar to the HTTP header concept.
|
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||||
/// Hint: MQTT 5 feature only.
|
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||||
|
/// 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,17 +6,100 @@ using System;
|
|||||||
|
|
||||||
namespace MQTTnet.Diagnostics
|
namespace MQTTnet.Diagnostics
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
|
||||||
|
*/
|
||||||
|
|
||||||
public static class MqttNetSourceLoggerExtensions
|
public static class MqttNetSourceLoggerExtensions
|
||||||
{
|
{
|
||||||
/*
|
public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
||||||
* The logger uses generic parameters in order to avoid boxing of parameter values like integers etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
|
|
||||||
{
|
{
|
||||||
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new MqttNetSourceLogger(logger, source);
|
logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, null, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error(this MqttNetSourceLogger logger, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Error, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1 }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, new object[] { parameter1, parameter2 }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info(this MqttNetSourceLogger logger, string message)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(MqttNetLogLevel.Info, message, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Publish<TParameter1>(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(logLevel, message, new object[] { parameter1 }, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Publish<TParameter1, TParameter2>(this MqttNetSourceLogger logger, MqttNetLogLevel logLevel, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
|
{
|
||||||
|
if (!logger.IsEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Publish(logLevel, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
public static void Verbose<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
@@ -26,7 +109,7 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Verbose<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
@@ -36,10 +119,14 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose<TParameter1, TParameter2, TParameter3>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2,
|
public static void Verbose<TParameter1, TParameter2, TParameter3>(
|
||||||
|
this MqttNetSourceLogger logger,
|
||||||
|
string message,
|
||||||
|
TParameter1 parameter1,
|
||||||
|
TParameter2 parameter2,
|
||||||
TParameter3 parameter3)
|
TParameter3 parameter3)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
@@ -47,7 +134,7 @@ namespace MQTTnet.Diagnostics
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] {parameter1, parameter2, parameter3}, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, new object[] { parameter1, parameter2, parameter3 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Verbose(this MqttNetSourceLogger logger, string message)
|
public static void Verbose(this MqttNetSourceLogger logger, string message)
|
||||||
@@ -60,134 +147,74 @@ namespace MQTTnet.Diagnostics
|
|||||||
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
|
logger.Publish(MqttNetLogLevel.Verbose, message, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, new object[] {parameter1, parameter2}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Info(this MqttNetSourceLogger logger, string message)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Info, message, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message)
|
public static void Warning(this MqttNetSourceLogger logger, Exception exception, string message)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
|
logger.Publish(MqttNetLogLevel.Warning, message, null, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
public static void Warning<TParameter1>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1}, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
public static void Warning<TParameter1, TParameter2>(this MqttNetSourceLogger logger, string message, TParameter1 parameter1, TParameter2 parameter2)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, new object[] {parameter1, parameter2}, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, new object[] { parameter1, parameter2 }, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning(this MqttNetSourceLogger logger, string message)
|
public static void Warning(this MqttNetSourceLogger logger, string message)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (!logger.IsEnabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
|
logger.Publish(MqttNetLogLevel.Warning, message, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error<TParameter1>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1)
|
public static MqttNetSourceLogger WithSource(this IMqttNetLogger logger, string source)
|
||||||
{
|
{
|
||||||
if (!logger.IsEnabled)
|
if (logger == null)
|
||||||
{
|
{
|
||||||
return;
|
throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1}, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error<TParameter1, TParameter2>(this MqttNetSourceLogger logger, Exception exception, string message, TParameter1 parameter1, TParameter2 parameter2)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, new object[] {parameter1, parameter2}, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(this MqttNetSourceLogger logger, Exception exception, string message)
|
return new MqttNetSourceLogger(logger, source);
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, null, exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(this MqttNetSourceLogger logger, string message)
|
|
||||||
{
|
|
||||||
if (!logger.IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Publish(MqttNetLogLevel.Error, message, null, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,8 @@ namespace MQTTnet.Exceptions
|
|||||||
{
|
{
|
||||||
public class MqttCommunicationException : Exception
|
public class MqttCommunicationException : Exception
|
||||||
{
|
{
|
||||||
protected MqttCommunicationException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public MqttCommunicationException(Exception innerException)
|
public MqttCommunicationException(Exception innerException)
|
||||||
: base(innerException.Message, innerException)
|
: base(innerException?.Message ?? "MQTT communication failed.", innerException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ using MQTTnet.Packets;
|
|||||||
|
|
||||||
namespace MQTTnet.Formatter
|
namespace MQTTnet.Formatter
|
||||||
{
|
{
|
||||||
public sealed class MqttApplicationMessageFactory
|
public static class MqttApplicationMessageFactory
|
||||||
{
|
{
|
||||||
public MqttApplicationMessage Create(MqttPublishPacket publishPacket)
|
public static MqttApplicationMessage Create(MqttPublishPacket publishPacket)
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user