Compare commits

...
Author SHA1 Message Date
Christian de647f650c Add unit test to reproduce issue 2022-11-07 19:51:44 +01:00
Christian 5c74728387 Fix client session takeover 2022-11-06 13:28:41 +01:00
Christian 97c46bea0f Update ReleaseNotes.md 2022-11-06 10:50:32 +01:00
Christian e6c1ab4e4c Fail all waiting tasks when the client connection gets closed (#1563) 2022-11-06 10:50:04 +01:00
Christian 6e1ec266a7 Update ReleaseNotes.md 2022-11-06 10:49:19 +01:00
Christian 3fc6982e9a Fix upgrading and downgrading of retained messages QoS for subscriptions (#1564) 2022-11-06 10:35:59 +01:00
SilverFox 5b1cc7d713 Also cleanup MqttClientSessionsManager._subscriberSessions when session is taken over by a new one with CleanSession=true, fix #1552 (#1553) 2022-11-06 10:35:15 +01:00
Christian 2842f27522 1552 mqttnetservermqttclientsessionsmanager subscribersessions is not proper removed on special case (#1562) 2022-11-06 10:34:20 +01:00
Christian 7ef137e26e No longer fire _ClientAcknowledgedPublishPacketEvent_ for PUBREC packets (#1550) (#1551) 2022-11-06 10:29:49 +01:00
Christian b1a336eb62 Reset release notes, update version 2022-11-06 10:24:58 +01:00
Christian d481e93832 Add new samples. 2022-10-25 17:29:45 +02:00
Christian 8cf5537043 Improve unit tests 2022-10-23 08:45:16 +02:00
Christian d2ce9d607a Fix freezes in _AsyncLock_ (#1549) 2022-10-23 08:16:45 +02:00
Christian 36736914bc Fix Xamarin connect freeze (#1545) 2022-10-23 07:51:19 +02:00
Christian 7823d8342e Update version. 2022-10-23 07:43:31 +02:00
Christian 431142471f Add samples. 2022-10-16 11:12:08 +02:00
Christian a3227a444a Reset release notes. 2022-10-01 16:51:11 +02:00
104 changed files with 2280 additions and 1384 deletions
+5 -7
View File
@@ -1,7 +1,5 @@
* [Core] MQTT Packets being sent over web socket transport are now setting the web socket frame boundaries correctly (#1499). * [Core] Fixed several dead locks by implementing a new async signal (#1552)
* [Core] Add support for attaching and detaching events from different threads. * [Client] All waiting tasks will now fail when the client connection gets closed. This fixes several freezes (#1561)
* [Core] Fixed a deadlock in _AsyncLock_ implementation (#1520). * [Server] Fixed duplicated invocation of the event _ClientAcknowledgedPublishPacketAsync_ for QoS level 2 (#1550)
* [Client] Keep alive mechanism now uses the configured timeout value from the options (thanks to @Stannieman, #1495). * [Server] Fixed issue in upgrading and downgrading of QoS levels for subscriptions and retained messages (#1560)
* [Client] The _PingAsync_ will fallback to the timeout specified in the client options when the cancellation token cannot be cancelled. * [Server] Fixed memory leak when old sessions are discarded (#1553)
* [Server] A DISCONNECT packet is no longer sent to MQTT clients < 5.0.0 (thanks to @logicaloud, #1506).
* [Server] Improved "take over" process handling.
+1 -1
View File
@@ -3,7 +3,7 @@ name: CI
on: [push, pull_request] on: [push, pull_request]
env: env:
VERSION: "4.1.1.${{github.run_number}}" VERSION: "4.1.3.${{github.run_number}}"
jobs: jobs:
build: build:
+1
View File
@@ -242,5 +242,6 @@ 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_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/=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/=PUBCOMP/@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></wpf:ResourceDictionary>
+53 -10
View File
@@ -119,7 +119,7 @@ public static class Client_Connection_Samples
// The used public broker sometimes has invalid certificates. This sample accepts all // The used public broker sometimes has invalid certificates. This sample accepts all
// certificates. This should not be used in live environments. // certificates. This should not be used in live environments.
o.CertificateValidationHandler = _ => true; o.CertificateValidationHandler = _ => true;
// The default value is determined by the OS. Set manually to force version. // The default value is determined by the OS. Set manually to force version.
o.SslProtocol = SslProtocols.Tls12; o.SslProtocol = SslProtocols.Tls12;
}) })
@@ -134,15 +134,17 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Connect_Client_Using_WebSockets() public static async Task Connect_Client_Using_WebSocket4Net()
{ {
/* /*
* 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
* 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.
*/ */
var mqttFactory = new MqttFactory(); var mqttFactory = new MqttFactory().UseWebSocket4Net();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -155,18 +157,16 @@ public static class Client_Connection_Samples
response.DumpToConsole(); response.DumpToConsole();
} }
} }
public static async Task Connect_Client_Using_WebSocket4Net() public static async Task Connect_Client_Using_WebSockets()
{ {
/* /*
* 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
* 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.
*/ */
var mqttFactory = new MqttFactory().UseWebSocket4Net(); var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
@@ -206,7 +206,7 @@ public static class Client_Connection_Samples
using (var timeout = new CancellationTokenSource(5000)) using (var timeout = new CancellationTokenSource(5000))
{ {
var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token); var response = await mqttClient.ConnectAsync(mqttClientOptions, timeout.Token);
Console.WriteLine("The MQTT client is connected."); Console.WriteLine("The MQTT client is connected.");
response.DumpToConsole(); response.DumpToConsole();
@@ -214,6 +214,49 @@ public static class Client_Connection_Samples
} }
} }
public static async Task Disconnect_Clean()
{
/*
* This sample disconnects from the server with sending a DISCONNECT packet.
* This way of disconnecting is treated as a clean disconnect which will not
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _DisconnectAsync_ will send a DISCONNECT packet before closing the connection.
// Using a reason code requires MQTT version 5.0.0!
await mqttClient.DisconnectAsync(MqttClientDisconnectReason.ImplementationSpecificError);
}
}
public static async Task Disconnect_Non_Clean()
{
/*
* This sample disconnects from the server without sending a DISCONNECT packet.
* This way of disconnecting is treated as a non clean disconnect which will
* trigger sending the last will etc.
*/
var mqttFactory = new MqttFactory();
var mqttClient = mqttFactory.CreateMqttClient();
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Calling _Dispose_ or use of a _using_ statement will close the transport connection
// without sending a DISCONNECT packet to the server.
mqttClient.Dispose();
}
public static async Task Inspect_Certificate_Validation_Errors() public static async Task Inspect_Certificate_Validation_Errors()
{ {
/* /*
@@ -248,7 +291,7 @@ public static class Client_Connection_Samples
} }
} }
} }
public static async Task Ping_Server() public static async Task Ping_Server()
{ {
/* /*
+90 -40
View File
@@ -8,6 +8,7 @@
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Samples.Helpers; using MQTTnet.Samples.Helpers;
namespace MQTTnet.Samples.Client; namespace MQTTnet.Samples.Client;
@@ -24,9 +25,7 @@ public static class Client_Subscribe_Samples
using (var mqttClient = mqttFactory.CreateMqttClient()) using (var mqttClient = mqttFactory.CreateMqttClient())
{ {
var mqttClientOptions = new MqttClientOptionsBuilder() var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
.WithTcpServer("broker.hivemq.com")
.Build();
// Setup message handling before connecting so that queued messages // Setup message handling before connecting so that queued messages
// are also handled properly. When there is no event handler attached all // are also handled properly. When there is no event handler attached all
@@ -42,7 +41,11 @@ public static class Client_Subscribe_Samples
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/2"); }) .WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/2");
})
.Build(); .Build();
await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -54,35 +57,6 @@ public static class Client_Subscribe_Samples
} }
} }
public static async Task Subscribe_Topic()
{
/*
* This sample subscribes to a topic.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); })
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Send_Responses() public static async Task Send_Responses()
{ {
/* /*
@@ -100,22 +74,98 @@ public static class Client_Subscribe_Samples
// Now respond to the broker with a reason code other than success. // Now respond to the broker with a reason code other than success.
args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError; args.ReasonCode = MqttApplicationMessageReceivedReasonCode.ImplementationSpecificError;
args.ResponseReasonString = "That did not work!"; args.ResponseReasonString = "That did not work!";
// User properties require MQTT v5! // User properties require MQTT v5!
args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data")); args.ResponseUserProperties.Add(new MqttUserProperty("My", "Data"));
// Now the broker will resend the message again. // Now the broker will resend the message again.
return Task.CompletedTask; return Task.CompletedTask;
}; };
var mqttClientOptions = new MqttClientOptionsBuilder() var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
.WithTcpServer("broker.hivemq.com")
.Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder() var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(f => { f.WithTopic("mqttnet/samples/topic/1"); }) .WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topic.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Multiple_Topics()
{
/*
* This sample subscribes to several topics in a single request.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
// Create the subscribe options including several topics with different options.
// It is also possible to all of these topics using a dedicated call of _SubscribeAsync_ per topic.
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/2").WithNoLocal();
})
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/3").WithRetainHandling(MqttRetainHandling.SendAtSubscribe);
})
.Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
Console.WriteLine("MQTT client subscribed to topics.");
// The response contains additional data sent by the server after subscribing.
response.DumpToConsole();
}
}
public static async Task Subscribe_Topic()
{
/*
* This sample subscribes to a topic.
*/
var mqttFactory = new MqttFactory();
using (var mqttClient = mqttFactory.CreateMqttClient())
{
var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer("broker.hivemq.com").Build();
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
var mqttSubscribeOptions = mqttFactory.CreateSubscribeOptionsBuilder()
.WithTopicFilter(
f =>
{
f.WithTopic("mqttnet/samples/topic/1");
})
.Build(); .Build();
var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None); var response = await mqttClient.SubscribeAsync(mqttSubscribeOptions, CancellationToken.None);
@@ -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();
}
}
}
@@ -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,22 +2,21 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using 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.Extensions;
@@ -26,20 +25,6 @@ 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, new byte[0]));
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()
@@ -171,7 +171,7 @@ namespace MQTTnet.AspNetCore
{ {
var formatter = PacketFormatterAdapter; var formatter = PacketFormatterAdapter;
using (await _writerLock.WaitAsync(cancellationToken).ConfigureAwait(false)) using (await _writerLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{ {
var buffer = formatter.Encode(packet); var buffer = formatter.Encode(packet);
var msg = buffer.Join().AsMemory(); var msg = buffer.Join().AsMemory();
@@ -29,7 +29,7 @@ namespace MQTTnet.Benchmarks
tasks[i] = Task.Run( tasks[i] = Task.Run(
async () => async () =>
{ {
using (await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await asyncLock.EnterAsync().ConfigureAwait(false))
{ {
var localI = globalI; var localI = globalI;
await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data. await Task.Delay(5).ConfigureAwait(false); // Increase the chance for wrong data.
@@ -56,7 +56,7 @@ namespace MQTTnet.Benchmarks
{ {
for (var i = 0; i < 100000; i++) for (var i = 0; i < 100000; i++)
{ {
using (await asyncLock.WaitAsync(cancellationToken.Token).ConfigureAwait(false)) using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{ {
} }
} }
@@ -167,7 +167,7 @@ namespace MQTTnet.Extensions.ManagedClient
try try
{ {
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _messageQueueLock.EnterAsync().ConfigureAwait(false))
{ {
if (_messageQueue.Count >= Options.MaxPendingMessages) if (_messageQueue.Count >= Options.MaxPendingMessages)
{ {
@@ -296,7 +296,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release(); _subscriptionsQueuedSignal.Release();
return Task.FromResult(0); return CompletedTask.Instance;
} }
public Task UnsubscribeAsync(ICollection<string> topics) public Task UnsubscribeAsync(ICollection<string> topics)
@@ -319,7 +319,7 @@ namespace MQTTnet.Extensions.ManagedClient
_subscriptionsQueuedSignal.Release(); _subscriptionsQueuedSignal.Release();
return Task.FromResult(0); return CompletedTask.Instance;
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -693,7 +693,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false); await InternalClient.PublishAsync(message.ApplicationMessage).ConfigureAwait(false);
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{ {
// While publishing this message, this.PublishAsync could have booted this // While publishing this message, this.PublishAsync could have booted this
// message off the queue to make room for another (when using a cap // message off the queue to make room for another (when using a cap
@@ -724,7 +724,7 @@ namespace MQTTnet.Extensions.ManagedClient
//contradict the expected behavior of QoS 1 and 2, that's also true //contradict the expected behavior of QoS 1 and 2, that's also true
//for the usage of a message queue cap, so it's still consistent //for the usage of a message queue cap, so it's still consistent
//with prior behavior in that way. //with prior behavior in that way.
using (await _messageQueueLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync using (await _messageQueueLock.EnterAsync().ConfigureAwait(false)) //lock to avoid conflict with this.PublishAsync
{ {
_messageQueue.RemoveFirst(i => i.Id.Equals(message.Id)); _messageQueue.RemoveFirst(i => i.Id.Equals(message.Id));
@@ -34,7 +34,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{ {
_messages.Add(applicationMessage); _messages.Add(applicationMessage);
await SaveAsync().ConfigureAwait(false); await SaveAsync().ConfigureAwait(false);
@@ -45,7 +45,7 @@ namespace MQTTnet.Extensions.ManagedClient
{ {
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
using (await _messagesLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _messagesLock.EnterAsync().ConfigureAwait(false))
{ {
var index = _messages.IndexOf(applicationMessage); var index = _messages.IndexOf(applicationMessage);
if (index == -1) if (index == -1)
@@ -9,7 +9,7 @@ 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.Implementations; using MQTTnet.Internal;
namespace MQTTnet.Extensions.Rpc namespace MQTTnet.Extensions.Rpc
{ {
@@ -116,7 +116,7 @@ namespace MQTTnet.Extensions.Rpc
{ {
if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable)) if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable))
{ {
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
#if NET452 #if NET452
@@ -128,7 +128,7 @@ namespace MQTTnet.Extensions.Rpc
// 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 PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
public void Dispose() public void Dispose()
@@ -14,6 +14,7 @@ using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Internal;
using SuperSocket.ClientEngine; using SuperSocket.ClientEngine;
using WebSocket4Net; using WebSocket4Net;
@@ -124,7 +125,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
_webSocket.Close(); _webSocket.Close();
} }
return Task.FromResult(0); return CompletedTask.Instance;
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -157,7 +158,7 @@ namespace MQTTnet.Extensions.WebSocket4Net
public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken) public Task WriteAsync(ArraySegment<byte> buffer, bool isEndOfPacket, CancellationToken cancellationToken)
{ {
_webSocket.Send(buffer.Array, buffer.Offset, buffer.Count); _webSocket.Send(buffer.Array, buffer.Offset, buffer.Count);
return Task.FromResult(0); return CompletedTask.Instance;
} }
public void Dispose() public void Dispose()
+1 -1
View File
@@ -18,7 +18,7 @@ namespace MQTTnet.TestApp
{ {
for (var i = 0; i < 100000; i++) for (var i = 0; i < 100000; i++)
{ {
using (await asyncLock.WaitAsync(cancellationToken.Token).ConfigureAwait(false)) using (await asyncLock.EnterAsync(cancellationToken.Token).ConfigureAwait(false))
{ {
} }
} }
+2 -2
View File
@@ -7,7 +7,7 @@ using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -40,7 +40,7 @@ namespace MQTTnet.TestApp
Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}"); Console.WriteLine($"+ Retain = {e.ApplicationMessage.Retain}");
Console.WriteLine(); Console.WriteLine();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
client.ConnectedAsync += async e => client.ConnectedAsync += async e =>
+3 -3
View File
@@ -9,7 +9,7 @@ using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -42,7 +42,7 @@ namespace MQTTnet.TestApp
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic); Console.WriteLine(">> RECEIVED: " + e.ApplicationMessage.Topic);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await managedClient.StartAsync(options); await managedClient.StartAsync(options);
@@ -85,7 +85,7 @@ namespace MQTTnet.TestApp
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages) public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{ {
File.WriteAllText(Filename, JsonConvert.SerializeObject(messages)); File.WriteAllText(Filename, JsonConvert.SerializeObject(messages));
return Task.FromResult(0); return CompletedTask.Instance;
} }
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync() public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
@@ -5,6 +5,7 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq; using System.Linq;
using MQTTnet.Internal;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
{ {
@@ -124,7 +125,7 @@ namespace MQTTnet.TestApp
{ {
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
} }
return Implementations.PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
public async Task Cleanup() public async Task Cleanup()
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Security.Authentication;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.TestApp namespace MQTTnet.TestApp
@@ -144,7 +144,7 @@ namespace MQTTnet.TestApp
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.ConnectAsync(options); await client.ConnectAsync(options);
+9 -11
View File
@@ -4,13 +4,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Internal;
using MQTTnet.Implementations;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -69,13 +67,13 @@ namespace MQTTnet.TestApp
} }
File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages)); File.WriteAllText(Filename, JsonConvert.SerializeObject(e.StoredRetainedMessages));
return Task.FromResult(0); return CompletedTask.Instance;
}; };
mqttServer.RetainedMessagesClearedAsync += e => mqttServer.RetainedMessagesClearedAsync += e =>
{ {
File.Delete(Filename); File.Delete(Filename);
return Task.FromResult(0); return CompletedTask.Instance;
}; };
mqttServer.LoadingRetainedMessageAsync += e => mqttServer.LoadingRetainedMessageAsync += e =>
@@ -93,7 +91,7 @@ namespace MQTTnet.TestApp
e.LoadedRetainedMessages = retainedMessages; e.LoadedRetainedMessages = retainedMessages;
return Task.FromResult(0); return CompletedTask.Instance;
}; };
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
@@ -111,7 +109,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true; e.CloseConnection = true;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
mqttServer.ValidatingConnectionAsync += e => mqttServer.ValidatingConnectionAsync += e =>
@@ -124,7 +122,7 @@ namespace MQTTnet.TestApp
} }
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
mqttServer.InterceptingSubscriptionAsync += e => mqttServer.InterceptingSubscriptionAsync += e =>
@@ -140,7 +138,7 @@ namespace MQTTnet.TestApp
e.CloseConnection = true; e.CloseConnection = true;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
mqttServer.InterceptingPublishAsync += e => mqttServer.InterceptingPublishAsync += e =>
@@ -149,7 +147,7 @@ namespace MQTTnet.TestApp
$"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'", $"'{e.ClientId}' reported '{e.ApplicationMessage.Topic}' > '{Encoding.UTF8.GetString(e.ApplicationMessage.Payload ?? new byte[0])}'",
ConsoleColor.Magenta); ConsoleColor.Magenta);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
//options.ApplicationMessageInterceptor = c => //options.ApplicationMessageInterceptor = c =>
@@ -177,7 +175,7 @@ namespace MQTTnet.TestApp
mqttServer.ClientConnectedAsync += e => mqttServer.ClientConnectedAsync += e =>
{ {
Console.Write("Client disconnected event fired."); Console.Write("Client disconnected event fired.");
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await mqttServer.StartAsync(); await mqttServer.StartAsync();
@@ -11,18 +11,62 @@ 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.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Tests.Client namespace MQTTnet.Tests.Client
{ {
[TestClass] [TestClass]
public sealed class ManagedMqttClient_Tests : BaseTestClass public sealed class ManagedMqttClient_Tests : BaseTestClass
{ {
[TestMethod]
public async Task Subscribe_When_Offline_But_Existing_Session()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer(o => o.WithPersistentSessions());
// The connected client will crate a new session.
var mqttClient = await testEnvironment.ConnectClient();
mqttClient.Dispose();
mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
// Subscribe before starting!
await managedClient.SubscribeAsync("a");
MqttApplicationMessage applicationMessage = null;
managedClient.ApplicationMessageReceivedAsync += e =>
{
applicationMessage = e.ApplicationMessage;
return CompletedTask.Instance;
};
// Now the client must send the new subscription.
await managedClient.StartAsync(
new ManagedMqttClientOptions
{
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder().WithCleanSession(false).WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build()
});
await LongTestDelay();
await server.InjectApplicationMessage(
new InjectedMqttApplicationMessage(
new MqttApplicationMessage
{
Topic = "a"
}));
await LongTestDelay();
Assert.IsNotNull(applicationMessage);
}
}
[TestMethod] [TestMethod]
public async Task Connect_To_Invalid_Server() public async Task Connect_To_Invalid_Server()
{ {
@@ -31,7 +75,6 @@ namespace MQTTnet.Tests.Client
testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreClientLogErrors = true;
var mqttClient = testEnvironment.CreateClient(); var mqttClient = testEnvironment.CreateClient();
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient); var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
ConnectingFailedEventArgs connectingFailedEventArgs = null; ConnectingFailedEventArgs connectingFailedEventArgs = null;
@@ -39,7 +82,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectingFailedAsync += args => managedClient.ConnectingFailedAsync += args =>
{ {
connectingFailedEventArgs = args; connectingFailedEventArgs = args;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await managedClient.StartAsync( await managedClient.StartAsync(
@@ -117,7 +160,7 @@ namespace MQTTnet.Tests.Client
receivingClient.ApplicationMessageReceivedAsync += e => receivingClient.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await receivingClient.SubscribeAsync("My/last/will"); await receivingClient.SubscribeAsync("My/last/will");
@@ -281,7 +324,7 @@ namespace MQTTnet.Tests.Client
}); });
// Wait a bit for the retained message to be available // Wait a bit for the retained message to be available
await Task.Delay(1000); await LongTestDelay();
await sendingClient.DisconnectAsync(); await sendingClient.DisconnectAsync();
@@ -290,28 +333,29 @@ namespace MQTTnet.Tests.Client
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort); var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>(); var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(testEnvironment.CreateClient());
managedClient.ApplicationMessageReceivedAsync += e => managedClient.ApplicationMessageReceivedAsync += e =>
{ {
receivedManagedMessages.Add(e.ApplicationMessage); receivedManagedMessages.Add(e.ApplicationMessage);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
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(1)).Build());
await Task.Delay(1000); await LongTestDelay();
Assert.AreEqual(1, receivedManagedMessages.Count); Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync(); await managedClient.StopAsync();
await Task.Delay(500); await LongTestDelay();
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build()); await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder().WithClientOptions(clientOptions).WithAutoReconnectDelay(TimeSpan.FromSeconds(1)).Build());
await Task.Delay(1000); 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,
// showing that the subscriptions were cleared // showing that the subscriptions were cleared
@@ -319,7 +363,9 @@ 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 Task.Delay(500);
await LongTestDelay();
Assert.AreEqual(2, receivedManagedMessages.Count); Assert.AreEqual(2, receivedManagedMessages.Count);
} }
} }
@@ -428,7 +474,7 @@ namespace MQTTnet.Tests.Client
managedClient.ConnectedAsync += e => managedClient.ConnectedAsync += e =>
{ {
connected.TrySetResult(true); connected.TrySetResult(true);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
return connected.Task; return connected.Task;
@@ -457,7 +503,7 @@ namespace MQTTnet.Tests.Client
result.TrySetResult(receivedMessages); result.TrySetResult(receivedMessages);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
return result.Task; return result.Task;
@@ -486,7 +532,7 @@ namespace MQTTnet.Tests.Client
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages) public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{ {
_messages = messages; _messages = messages;
return Task.FromResult(0); return CompletedTask.Instance;
} }
} }
} }
+20 -20
View File
@@ -14,7 +14,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -58,7 +58,7 @@ namespace MQTTnet.Tests.Client
} }
_ = InvokeInternal(); _ = InvokeInternal();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos)); var publishes = Task.WhenAll(publisher.PublishStringAsync("a", null, qos), publisher.PublishStringAsync("b", null, qos));
@@ -109,7 +109,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += args => client.DisconnectedAsync += args =>
{ {
disconnectHandlerCalled = true; disconnectHandlerCalled = true;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
@@ -123,7 +123,7 @@ namespace MQTTnet.Tests.Client
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException)); //Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
} }
await Task.Delay(100); // disconnected handler is called async await LongTestDelay(); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled); Assert.IsTrue(disconnectHandlerCalled);
} }
} }
@@ -138,7 +138,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
ex = e.Exception; ex = e.Exception;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
try try
@@ -170,7 +170,7 @@ namespace MQTTnet.Tests.Client
server.InterceptingPublishAsync += c => server.InterceptingPublishAsync += c =>
{ {
i++; i++;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.SendAsync( await client.SendAsync(
@@ -211,7 +211,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
handlerFired = true; handlerFired = true;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await server.StopAsync(); await server.StopAsync();
@@ -255,7 +255,7 @@ namespace MQTTnet.Tests.Client
receiveClient.ApplicationMessageReceivedAsync += e => receiveClient.ApplicationMessageReceivedAsync += e =>
{ {
receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await receiveClient.SubscribeAsync("x"); await receiveClient.SubscribeAsync("x");
@@ -313,7 +313,7 @@ namespace MQTTnet.Tests.Client
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await sender.PublishAsync(message.Build(), CancellationToken.None); await sender.PublishAsync(message.Build(), CancellationToken.None);
@@ -456,7 +456,7 @@ namespace MQTTnet.Tests.Client
receivedValues.Add(value); receivedValues.Add(value);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
client1.ApplicationMessageReceivedAsync += Handler1; client1.ApplicationMessageReceivedAsync += Handler1;
@@ -534,7 +534,7 @@ namespace MQTTnet.Tests.Client
client2.ApplicationMessageReceivedAsync += e => client2.ApplicationMessageReceivedAsync += e =>
{ {
client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload)); client2TopicResults.Add(Encoding.UTF8.GetString(e.ApplicationMessage.Payload));
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client2.SubscribeAsync(client2Topic); await client2.SubscribeAsync(client2Topic);
@@ -569,7 +569,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client1.SubscribeAsync("a"); await client1.SubscribeAsync("a");
@@ -728,7 +728,7 @@ namespace MQTTnet.Tests.Client
replies.Add(eventArgs.ApplicationMessage.Topic); replies.Add(eventArgs.ApplicationMessage.Topic);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
client2.ApplicationMessageReceivedAsync += Handler2; client2.ApplicationMessageReceivedAsync += Handler2;
@@ -766,7 +766,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true; replyReceived = true;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
client2.ApplicationMessageReceivedAsync += async e => client2.ApplicationMessageReceivedAsync += async e =>
@@ -817,7 +817,7 @@ namespace MQTTnet.Tests.Client
replyReceived = true; replyReceived = true;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce); await client.PublishStringAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
@@ -840,7 +840,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
Assert.IsTrue(e.ClientWasConnected); Assert.IsTrue(e.ClientWasConnected);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.DisconnectAsync(); await client.DisconnectAsync();
@@ -860,7 +860,7 @@ namespace MQTTnet.Tests.Client
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
Assert.IsTrue(e.ClientWasConnected); Assert.IsTrue(e.ClientWasConnected);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await server.StopAsync(); await server.StopAsync();
@@ -897,7 +897,7 @@ namespace MQTTnet.Tests.Client
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build()); await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build());
@@ -922,14 +922,14 @@ namespace MQTTnet.Tests.Client
client1.DisconnectedAsync += c => client1.DisconnectedAsync += c =>
{ {
disconnectedFired = true; disconnectedFired = true;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var messageReceived = false; var messageReceived = false;
client1.ApplicationMessageReceivedAsync += e => client1.ApplicationMessageReceivedAsync += e =>
{ {
messageReceived = true; messageReceived = true;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build()); await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build());
@@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations; using MQTTnet.Internal;
namespace MQTTnet.Tests.Diagnostics namespace MQTTnet.Tests.Diagnostics
{ {
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Diagnostics
mqttClient.InspectPackage += eventArgs => mqttClient.InspectPackage += eventArgs =>
{ {
packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer)); packets.Add(eventArgs.Direction + ":" + Convert.ToBase64String(eventArgs.Buffer));
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None); await mqttClient.ConnectAsync(mqttClientOptions, CancellationToken.None);
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient; using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Server; using MQTTnet.Server;
namespace MQTTnet.Tests.Factory namespace MQTTnet.Tests.Factory
@@ -56,7 +56,7 @@ namespace MQTTnet.Tests.Factory
managedClient.ConnectingFailedAsync += e => managedClient.ConnectingFailedAsync += e =>
{ {
tcs.TrySetResult(null); tcs.TrySetResult(null);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.Timeout), tcs.Task); await Task.WhenAny(Task.Delay(managedClient.Options.ClientOptions.Timeout), tcs.Task);
@@ -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.Extensions;
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
@@ -6,7 +6,6 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal namespace MQTTnet.Tests.Internal
@@ -16,6 +15,28 @@ namespace MQTTnet.Tests.Internal
{ {
int _testEventAsyncCount; int _testEventAsyncCount;
[TestMethod]
public async Task Attach_and_Detach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
testClass.TestEvent -= OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
testClass.TestEventAsync -= OnTestEventAsync;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(3, _testEventAsyncCount);
}
[TestMethod] [TestMethod]
public async Task Attach_EventHandler() public async Task Attach_EventHandler()
{ {
@@ -28,6 +49,19 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(1, _testEventAsyncCount); Assert.AreEqual(1, _testEventAsyncCount);
} }
[TestMethod]
public async Task Attach_Sync_And_Async_EventHandler()
{
var testClass = new TestClass();
testClass.TestEventAsync += OnTestEventAsync;
testClass.TestEvent += OnTestEvent;
await testClass.FireEventAsync(EventArgs.Empty);
Assert.AreEqual(2, _testEventAsyncCount);
}
[TestMethod] [TestMethod]
public async Task Detach_EventHandler() public async Task Detach_EventHandler()
{ {
@@ -68,16 +102,27 @@ namespace MQTTnet.Tests.Internal
Assert.AreEqual(false, testClass.HasTestHandlers); Assert.AreEqual(false, testClass.HasTestHandlers);
} }
void OnTestEvent(EventArgs arg)
{
Interlocked.Increment(ref _testEventAsyncCount);
}
Task OnTestEventAsync(EventArgs arg) Task OnTestEventAsync(EventArgs arg)
{ {
Interlocked.Increment(ref _testEventAsyncCount); Interlocked.Increment(ref _testEventAsyncCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
sealed class TestClass sealed class TestClass
{ {
readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>(); readonly AsyncEvent<EventArgs> _testEvent = new AsyncEvent<EventArgs>();
public event Action<EventArgs> TestEvent
{
add => _testEvent.AddHandler(value);
remove => _testEvent.RemoveHandler(value);
}
public event Func<EventArgs, Task> TestEventAsync public event Func<EventArgs, Task> TestEventAsync
{ {
add => _testEvent.AddHandler(value); add => _testEvent.AddHandler(value);
+159 -19
View File
@@ -12,26 +12,130 @@ using MQTTnet.Internal;
namespace MQTTnet.Tests.Internal namespace MQTTnet.Tests.Internal
{ {
[TestClass] [TestClass]
public class AsyncLock_Tests public sealed class AsyncLock_Tests
{ {
[TestMethod] [TestMethod]
public void Lock_10_Parallel_Tasks() public async Task Cancellation_Of_Awaiter()
{ {
const int ThreadsCount = 10;
var threads = new Task[ThreadsCount];
var @lock = new AsyncLock(); var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
var counter = 0;
Debug.WriteLine("Prepared locked lock.");
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 1 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 2 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
// SHOULD GET TIMEOUT!
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 3 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(4)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 4 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 5 incremented");
counter++;
}
}
});
_ = Task.Run(
async () =>
{
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(6)))
{
using (await @lock.EnterAsync(timeout.Token))
{
Debug.WriteLine("Task 6 incremented");
counter++;
}
}
});
Debug.WriteLine("Delay before release...");
await Task.Delay(TimeSpan.FromSeconds(3.1));
releaser.Dispose();
Debug.WriteLine("Wait for all tasks...");
await Task.Delay(TimeSpan.FromSeconds(6.1));
Assert.AreEqual(3, counter);
}
[TestMethod]
public void Lock_Parallel_Tasks()
{
const int taskCount = 50;
var @lock = new AsyncLock();
var tasks = new Task[taskCount];
var globalI = 0; var globalI = 0;
for (var i = 0; i < ThreadsCount; i++) for (var i = 0; i < taskCount; i++)
{ {
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
threads[i] = Task.Run( tasks[i] = Task.Run(
async () => async () =>
{ {
using (await @lock.WaitAsync(CancellationToken.None)) using (await @lock.EnterAsync())
{ {
var localI = globalI; var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data. await Task.Delay(5); // Increase the chance for wrong data.
localI++; localI++;
globalI = localI; globalI = localI;
} }
@@ -39,8 +143,8 @@ namespace MQTTnet.Tests.Internal
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
} }
Task.WaitAll(threads); Task.WaitAll(tasks);
Assert.AreEqual(ThreadsCount, globalI); Assert.AreEqual(taskCount, globalI);
} }
[TestMethod] [TestMethod]
@@ -57,7 +161,7 @@ namespace MQTTnet.Tests.Internal
threads[i] = Task.Run( threads[i] = Task.Run(
async () => async () =>
{ {
using (await @lock.WaitAsync(CancellationToken.None)) using (await @lock.EnterAsync())
{ {
var localI = globalI; var localI = globalI;
await Task.Delay(10); // Increase the chance for wrong data. await Task.Delay(10); // Increase the chance for wrong data.
@@ -96,7 +200,7 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock(); var @lock = new AsyncLock();
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
{ {
using (await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await @lock.EnterAsync().ConfigureAwait(false))
{ {
sum++; sum++;
} }
@@ -112,11 +216,11 @@ namespace MQTTnet.Tests.Internal
var @lock = new AsyncLock(); var @lock = new AsyncLock();
// This call will never "release" the lock due to missing _using_. // This call will never "release" the lock due to missing _using_.
await @lock.WaitAsync(CancellationToken.None).ConfigureAwait(false); await @lock.EnterAsync().ConfigureAwait(false);
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3))) using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{ {
await @lock.WaitAsync(cts.Token).ConfigureAwait(false); await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
} }
} }
@@ -125,15 +229,15 @@ namespace MQTTnet.Tests.Internal
{ {
var asyncLock = new AsyncLock(); var asyncLock = new AsyncLock();
var releaser = await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); var releaser = await asyncLock.EnterAsync().ConfigureAwait(false);
try try
{ {
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3))) using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{ {
await asyncLock.WaitAsync(timeout.Token).ConfigureAwait(false); await asyncLock.EnterAsync(timeout.Token).ConfigureAwait(false);
} }
Assert.Fail("Exception should be thrown!"); Assert.Fail("Exception should be thrown!");
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -142,10 +246,46 @@ namespace MQTTnet.Tests.Internal
releaser.Dispose(); releaser.Dispose();
using (await asyncLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await asyncLock.EnterAsync(CancellationToken.None).ConfigureAwait(false))
{ {
// When the method finished, the thread got access. // When the method finished, the thread got access.
} }
} }
[TestMethod]
public async Task Use_After_Cancellation()
{
var @lock = new AsyncLock();
// This call will not yet "release" the lock due to missing _using_.
var releaser = await @lock.EnterAsync().ConfigureAwait(false);
try
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
await @lock.EnterAsync(cts.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// Expected exception!
}
releaser.Dispose();
// Regular usage after cancellation.
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
using (await @lock.EnterAsync().ConfigureAwait(false))
{
}
}
} }
} }
@@ -0,0 +1,114 @@
// 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 Loop_Signal()
{
var asyncSignal = new AsyncSignal();
for (var i = 0; i < 10; i++)
{
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();
}
}
}
@@ -15,7 +15,7 @@ namespace MQTTnet.Tests.Internal
public sealed class MqttPacketBus_Tests public sealed class MqttPacketBus_Tests
{ {
[TestMethod] [TestMethod]
[ExpectedException(typeof(OperationCanceledException))] [ExpectedException(typeof(TaskCanceledException))]
public async Task Wait_With_Empty_Bus() public async Task Wait_With_Empty_Bus()
{ {
var bus = new MqttPacketBus(); var bus = new MqttPacketBus();
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net452;net461;netcoreapp3.1;net6.0</TargetFrameworks> <TargetFrameworks>net452;net461;net48;netcoreapp3.1;net6.0</TargetFrameworks>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<EnableNETAnalyzers>false</EnableNETAnalyzers> <EnableNETAnalyzers>false</EnableNETAnalyzers>
+3 -4
View File
@@ -8,10 +8,9 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
{ {
@@ -43,7 +42,7 @@ namespace MQTTnet.Tests.MQTTv5
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.PublishAsync(new MqttApplicationMessageBuilder() await client.PublishAsync(new MqttApplicationMessageBuilder()
@@ -245,7 +244,7 @@ namespace MQTTnet.Tests.MQTTv5
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+9 -10
View File
@@ -8,7 +8,7 @@ using MQTTnet.Formatter;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
@@ -31,7 +31,7 @@ namespace MQTTnet.Tests.MQTTv5
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -171,7 +171,6 @@ namespace MQTTnet.Tests.MQTTv5
// Session should be present // Session should be present
Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not"); Assert.IsTrue(!result3.IsSessionPresent, "Session is present when it should not");
} }
} }
@@ -180,7 +179,7 @@ namespace MQTTnet.Tests.MQTTv5
{ {
using (var testEnvironment = CreateTestEnvironment()) using (var testEnvironment = CreateTestEnvironment())
{ {
var disconnectReason = MQTTnet.Client.MqttClientDisconnectReason.UnspecifiedError; var disconnectReason = MqttClientDisconnectReason.UnspecifiedError;
string testClientId = null; string testClientId = null;
@@ -189,15 +188,15 @@ namespace MQTTnet.Tests.MQTTv5
testEnvironment.Server.ClientConnectedAsync += e => testEnvironment.Server.ClientConnectedAsync += e =>
{ {
testClientId = e.ClientId; testClientId = e.ClientId;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(new MQTTnet.Client.MqttClientOptionsBuilder().WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)); var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
client.DisconnectedAsync += e => client.DisconnectedAsync += e =>
{ {
disconnectReason = e.Reason; disconnectReason = e.Reason;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await LongTestDelay(); await LongTestDelay();
@@ -208,17 +207,17 @@ namespace MQTTnet.Tests.MQTTv5
// Have the server disconnect the client with AdministrativeAction reason // Have the server disconnect the client with AdministrativeAction reason
await testEnvironment.Server.DisconnectClientAsync(testClientId, Protocol.MqttDisconnectReasonCode.AdministrativeAction); await testEnvironment.Server.DisconnectClientAsync(testClientId, MqttDisconnectReasonCode.AdministrativeAction);
await LongTestDelay(); await LongTestDelay();
// The reason should be returned to the client in the DISCONNECT packet // The reason should be returned to the client in the DISCONNECT packet
Assert.AreEqual(MQTTnet.Client.MqttClientDisconnectReason.AdministrativeAction, disconnectReason); Assert.AreEqual(MqttClientDisconnectReason.AdministrativeAction, disconnectReason);
} }
} }
MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval) static MqttClientOptions CreateClientOptions(TestEnvironment testEnvironment, string clientId, bool cleanSession, uint sessionExpiryInterval)
{ {
return testEnvironment.Factory.CreateClientOptionsBuilder() return testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500) .WithProtocolVersion(MqttProtocolVersion.V500)
@@ -8,6 +8,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Channel; using MQTTnet.Channel;
using MQTTnet.Internal;
namespace MQTTnet.Tests.Mockups namespace MQTTnet.Tests.Mockups
{ {
@@ -33,12 +34,12 @@ namespace MQTTnet.Tests.Mockups
public Task ConnectAsync(CancellationToken cancellationToken) public Task ConnectAsync(CancellationToken cancellationToken)
{ {
return Task.FromResult(0); return CompletedTask.Instance;
} }
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
{ {
return Task.FromResult(0); return CompletedTask.Instance;
} }
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
@@ -7,7 +7,7 @@ using System.Linq;
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.Implementations; using MQTTnet.Internal;
namespace MQTTnet.Tests.Mockups namespace MQTTnet.Tests.Mockups
{ {
@@ -46,7 +46,7 @@ namespace MQTTnet.Tests.Mockups
_receivedEventArgs.Add(eventArgs); _receivedEventArgs.Add(eventArgs);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
} }
} }
@@ -13,7 +13,7 @@ using MQTTnet.Client;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.LowLevelClient; using MQTTnet.LowLevelClient;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -220,7 +220,7 @@ namespace MQTTnet.Tests.Mockups
} }
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
return client; return client;
@@ -261,7 +261,7 @@ namespace MQTTnet.Tests.Mockups
} }
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
return Server; return Server;
+2 -1
View File
@@ -13,6 +13,7 @@ using MQTTnet.Extensions.Rpc;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
@@ -189,7 +190,7 @@ namespace MQTTnet.Tests
responseSender.ApplicationMessageReceivedAsync += async e => responseSender.ApplicationMessageReceivedAsync += async e =>
{ {
Assert.IsNull(e.ApplicationMessage.ResponseTopic); Assert.IsNull(e.ApplicationMessage.ResponseTopic);
await PlatformAbstractionLayer.CompletedTask; await CompletedTask.Instance;
}; };
var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); var requestSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
+2 -2
View File
@@ -8,7 +8,7 @@ using System.Diagnostics;
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.Implementations; using MQTTnet.Internal;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests namespace MQTTnet.Tests
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests
receiverClient.ApplicationMessageReceivedAsync += e => receiverClient.ApplicationMessageReceivedAsync += e =>
{ {
response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString()); response?.TrySetResult(e.ApplicationMessage.ConvertPayloadToString());
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await receiverClient.SubscribeAsync("#"); await receiverClient.SubscribeAsync("#");
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -47,20 +47,20 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.Success; e.ReasonCode = MqttConnectReasonCode.Success;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
testEnvironment.Server.ClientConnectedAsync += args => testEnvironment.Server.ClientConnectedAsync += args =>
{ {
serverConnectedClientId = args.ClientId; serverConnectedClientId = args.ClientId;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
testEnvironment.Server.ClientDisconnectedAsync += args => testEnvironment.Server.ClientDisconnectedAsync += args =>
{ {
serverDisconnectedClientId = args.ClientId; serverDisconnectedClientId = args.ClientId;
disconnectedMre.Set(); disconnectedMre.Set();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
// Arrange client // Arrange client
@@ -68,7 +68,7 @@ namespace MQTTnet.Tests.Server
client.ConnectedAsync += args => client.ConnectedAsync += args =>
{ {
clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier; clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
// Act // Act
+8 -8
View File
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -27,7 +27,7 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e => server.ClientConnectedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -54,7 +54,7 @@ namespace MQTTnet.Tests.Server
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
@@ -81,7 +81,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -108,7 +108,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e => server.ClientUnsubscribedTopicAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -134,7 +134,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -161,7 +161,7 @@ namespace MQTTnet.Tests.Server
server.StartedAsync += e => server.StartedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await server.StartAsync(); await server.StartAsync();
@@ -183,7 +183,7 @@ namespace MQTTnet.Tests.Server
server.StoppedAsync += e => server.StoppedAsync += e =>
{ {
eventArgs = e; eventArgs = e;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await server.StopAsync(); await server.StopAsync();
+32 -33
View File
@@ -11,8 +11,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Internal;
using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -99,7 +98,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e => server.ValidatingConnectionAsync += e =>
{ {
e.ReasonCode = MqttConnectReasonCode.NotAuthorized; e.ReasonCode = MqttConnectReasonCode.NotAuthorized;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var connectingFailedException = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => testEnvironment.ConnectClient()); var connectingFailedException = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(() => testEnvironment.ConnectClient());
@@ -122,7 +121,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError; e.Response.ReasonCode = MqttSubscribeReasonCode.UnspecifiedError;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
// Prepare some retained messages. // Prepare some retained messages.
@@ -146,7 +145,7 @@ namespace MQTTnet.Tests.Server
buffer.Append(e.ApplicationMessage.Topic); buffer.Append(e.ApplicationMessage.Topic);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client2.SubscribeAsync("y"); await client2.SubscribeAsync("y");
@@ -172,13 +171,13 @@ namespace MQTTnet.Tests.Server
server.ClientConnectedAsync += e => server.ClientConnectedAsync += e =>
{ {
Interlocked.Increment(ref clientConnectedCalled); Interlocked.Increment(ref clientConnectedCalled);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
{ {
Interlocked.Increment(ref clientDisconnectedCalled); Interlocked.Increment(ref clientDisconnectedCalled);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder());
@@ -225,7 +224,7 @@ namespace MQTTnet.Tests.Server
// Clear retained message. // Clear retained message.
await client.PublishAsync( await client.PublishAsync(
new MqttApplicationMessageBuilder().WithTopic("r" + i2) new MqttApplicationMessageBuilder().WithTopic("r" + i2)
.WithPayload(PlatformAbstractionLayer.EmptyByteArray) .WithPayload(EmptyBuffer.Array)
.WithRetainFlag() .WithRetainFlag()
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build()); .Build());
@@ -276,7 +275,7 @@ namespace MQTTnet.Tests.Server
{ {
e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" }; e.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" };
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
string receivedTopic = null; string receivedTopic = null;
@@ -286,7 +285,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += a => c1.ApplicationMessageReceivedAsync += a =>
{ {
receivedTopic = a.ApplicationMessage.Topic; receivedTopic = a.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("original_topic").Build());
@@ -305,7 +304,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended"); e.ApplicationMessage.Payload = Encoding.ASCII.GetBytes("extended");
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var c1 = await testEnvironment.ConnectClient(); var c1 = await testEnvironment.ConnectClient();
@@ -316,7 +315,7 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
{ {
isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(e.ApplicationMessage.Payload), StringComparison.Ordinal) == 0;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build());
@@ -339,7 +338,7 @@ namespace MQTTnet.Tests.Server
server.ApplicationMessageNotConsumedAsync += e => server.ApplicationMessageNotConsumedAsync += e =>
{ {
undeliverd = e.ApplicationMessage.Topic; undeliverd = e.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -376,7 +375,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await Task.Delay(500); await Task.Delay(500);
@@ -400,7 +399,7 @@ namespace MQTTnet.Tests.Server
s.RetainedMessageChangedAsync += e => s.RetainedMessageChangedAsync += e =>
{ {
savedRetainedMessages = e.StoredRetainedMessages; savedRetainedMessages = e.StoredRetainedMessages;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var c1 = await testEnvironment.ConnectClient(); var c1 = await testEnvironment.ConnectClient();
@@ -440,7 +439,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
receivedTopic = e.ApplicationMessage.Topic; receivedTopic = e.ApplicationMessage.Topic;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync("#"); await c1.SubscribeAsync("#");
@@ -487,7 +486,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce).Build();
@@ -521,13 +520,13 @@ namespace MQTTnet.Tests.Server
c2.ApplicationMessageReceivedAsync += e => c2.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
c3.ApplicationMessageReceivedAsync += e => c3.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false);
@@ -581,7 +580,7 @@ namespace MQTTnet.Tests.Server
events.Add("c"); events.Add("c");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
@@ -591,7 +590,7 @@ namespace MQTTnet.Tests.Server
events.Add("d"); events.Add("d");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString()); var clientOptionsBuilder = new MqttClientOptionsBuilder().WithClientId(Guid.NewGuid().ToString());
@@ -621,7 +620,7 @@ namespace MQTTnet.Tests.Server
events.Add("r"); events.Add("r");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c2.SubscribeAsync("topic"); await c2.SubscribeAsync("topic");
@@ -669,7 +668,7 @@ namespace MQTTnet.Tests.Server
server.ValidatingConnectionAsync += e => server.ValidatingConnectionAsync += e =>
{ {
ConnectionValidationHandler(e); ConnectionValidationHandler(e);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var events = new List<string>(); var events = new List<string>();
@@ -681,7 +680,7 @@ namespace MQTTnet.Tests.Server
events.Add("c"); events.Add("c");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
server.ClientDisconnectedAsync += e => server.ClientDisconnectedAsync += e =>
@@ -691,7 +690,7 @@ namespace MQTTnet.Tests.Server
events.Add("d"); events.Add("d");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id"); var clientOptions = new MqttClientOptionsBuilder().WithClientId("same_id");
@@ -706,7 +705,7 @@ namespace MQTTnet.Tests.Server
events.Add("x"); events.Add("x");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
@@ -716,7 +715,7 @@ namespace MQTTnet.Tests.Server
events.Add("r"); events.Add("r");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
c1.SubscribeAsync("topic").Wait(); c1.SubscribeAsync("topic").Wait();
@@ -785,7 +784,7 @@ namespace MQTTnet.Tests.Server
client1.ApplicationMessageReceivedAsync += e => client1.ApplicationMessageReceivedAsync += e =>
{ {
receivedBody = e.ApplicationMessage.Payload; receivedBody = e.ApplicationMessage.Payload;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client1.SubscribeAsync("string"); await client1.SubscribeAsync("string");
@@ -822,7 +821,7 @@ namespace MQTTnet.Tests.Server
receivedMessages.Add(e.ApplicationMessage); receivedMessages.Add(e.ApplicationMessage);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await Task.Delay(500); await Task.Delay(500);
@@ -850,7 +849,7 @@ namespace MQTTnet.Tests.Server
c1.DisconnectedAsync += e => c1.DisconnectedAsync += e =>
{ {
disconnectCalled++; disconnectCalled++;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await Task.Delay(100); await Task.Delay(100);
@@ -950,7 +949,7 @@ namespace MQTTnet.Tests.Server
testEnvironment.Server.ClientConnectedAsync += e => testEnvironment.Server.ClientConnectedAsync += e =>
{ {
testClientId = e.ClientId; testClientId = e.ClientId;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
@@ -959,12 +958,12 @@ namespace MQTTnet.Tests.Server
{ {
if (e.Buffer.Length > 0) if (e.Buffer.Length > 0)
{ {
if (e.Buffer[0] == (byte)Protocol.MqttControlPacketType.Disconnect << 4) if (e.Buffer[0] == (byte)MqttControlPacketType.Disconnect << 4)
{ {
disconnectPacketReceived = true; disconnectPacketReceived = true;
} }
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.ConnectAsync( await client.ConnectAsync(
+5 -5
View File
@@ -3,7 +3,7 @@ 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;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
receiverClient.ApplicationMessageReceivedAsync += e => receiverClient.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await receiverClient.SubscribeAsync("t/+"); await receiverClient.SubscribeAsync("t/+");
@@ -55,7 +55,7 @@ namespace MQTTnet.Tests.Server
}); });
} }
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(60)); SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(90));
Assert.AreEqual(100000, receivedMessages); Assert.AreEqual(100000, receivedMessages);
} }
@@ -74,7 +74,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
@@ -137,7 +137,7 @@ namespace MQTTnet.Tests.Server
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
Interlocked.Increment(ref receivedMessages); Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
for (var i = 0; i < 100; i++) for (var i = 0; i < 100; i++)
@@ -130,9 +130,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,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);
}
}
}
}
+6 -15
View File
@@ -6,7 +6,7 @@ using System.Collections.Generic;
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.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args => server.ClientAcknowledgedPublishPacketAsync += args =>
{ {
eventArgs = args; eventArgs = args;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
server.ClientAcknowledgedPublishPacketAsync += args => server.ClientAcknowledgedPublishPacketAsync += args =>
{ {
eventArgs = args; eventArgs = args;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -88,7 +88,7 @@ namespace MQTTnet.Tests.Server
eventArgs.Add(args); eventArgs.Add(args);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client1 = await testEnvironment.ConnectClient(); var client1 = await testEnvironment.ConnectClient();
@@ -99,25 +99,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.
@@ -8,7 +8,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
publishedApplicationMessages.Add(eventArgs.ApplicationMessage); publishedApplicationMessages.Add(eventArgs.ApplicationMessage);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
testEnvironment.Server.ValidatingConnectionAsync += eventArgs => testEnvironment.Server.ValidatingConnectionAsync += eventArgs =>
@@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Server
eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized; eventArgs.ReasonCode = MqttConnectReasonCode.NotAuthorized;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
using (var validClient = testEnvironment.CreateClient()) using (var validClient = testEnvironment.CreateClient())
@@ -157,7 +157,7 @@ namespace MQTTnet.Tests.Server
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = testEnvironment.CreateClient(); var client = testEnvironment.CreateClient();
@@ -8,6 +8,7 @@ using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -28,7 +29,7 @@ namespace MQTTnet.Tests.Server
{ {
e.ReasonCode = MqttConnectReasonCode.ServerMoved; e.ReasonCode = MqttConnectReasonCode.ServerMoved;
e.ServerReference = "new_server"; e.ServerReference = "new_server";
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
try try
+31 -41
View File
@@ -10,9 +10,8 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -29,7 +28,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(o => o.WithPersistentSessions()); await testEnvironment.StartServer(o => o.WithPersistentSessions());
const string ClientId = "Client1"; const string clientId = "Client1";
// Create client with clean session and long session expiry interval // Create client with clean session and long session expiry interval
@@ -38,7 +37,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort) .WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession() // start and end with clean session .WithCleanSession() // start and end with clean session
.WithClientId(ClientId) .WithClientId(clientId)
.Build()); .Build());
// Disconnect; empty session should be removed from server // Disconnect; empty session should be removed from server
@@ -57,7 +56,7 @@ namespace MQTTnet.Tests.Server
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort) .WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact .WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession(false) // see if there is a session .WithCleanSession(false) // see if there is a session
.WithClientId(ClientId) .WithClientId(clientId)
.Build(); .Build();
@@ -83,7 +82,7 @@ namespace MQTTnet.Tests.Server
server.SessionDeletedAsync += e => server.SessionDeletedAsync += e =>
{ {
deletedEventFired = true; deletedEventFired = true;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -111,7 +110,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true; e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World"; e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await testEnvironment.ConnectClient(); await testEnvironment.ConnectClient();
@@ -123,6 +122,8 @@ namespace MQTTnet.Tests.Server
} }
} }
#if !NET452
[TestMethod] [TestMethod]
[DataRow(MqttProtocolVersion.V311)] [DataRow(MqttProtocolVersion.V311)]
[DataRow(MqttProtocolVersion.V500)] [DataRow(MqttProtocolVersion.V500)]
@@ -134,7 +135,7 @@ namespace MQTTnet.Tests.Server
await testEnvironment.StartServer(); await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion).WithKeepAlivePeriod(TimeSpan.FromSeconds(5)); var options = new MqttClientOptionsBuilder().WithClientId("1").WithTimeout(TimeSpan.FromSeconds(1)).WithProtocolVersion(protocolVersion);
var hasReceive = false; var hasReceive = false;
@@ -149,14 +150,14 @@ 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();
await LongTestDelay(); await LongTestDelay();
var connectedClients = clients.Where(c => c != null).Where(c => c.TryPingAsync().GetAwaiter().GetResult()).ToList();
Assert.AreEqual(1, connectedClients.Count); Assert.AreEqual(1, connectedClients.Count);
await Task.Delay(5000);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
var sendClient = await testEnvironment.ConnectClient(option2); var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishStringAsync("aaa", "1"); await sendClient.PublishStringAsync("aaa", "1");
@@ -166,6 +167,7 @@ namespace MQTTnet.Tests.Server
Assert.AreEqual(true, hasReceive); Assert.AreEqual(true, hasReceive);
} }
} }
#endif
[DataTestMethod] [DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)] [DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
@@ -186,7 +188,7 @@ namespace MQTTnet.Tests.Server
c.AutoAcknowledge = false; c.AutoAcknowledge = false;
++count; ++count;
Console.WriteLine("process"); Console.WriteLine("process");
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await subscriber.SubscribeAsync("#", qos); await subscriber.SubscribeAsync("#", qos);
@@ -223,7 +225,7 @@ namespace MQTTnet.Tests.Server
client1.DisconnectedAsync += c => client1.DisconnectedAsync += c =>
{ {
disconnectReason = c.Reason; disconnectReason = c.Reason;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client2 = await testEnvironment.ConnectClient(options); var client2 = await testEnvironment.ConnectClient(options);
@@ -249,7 +251,7 @@ namespace MQTTnet.Tests.Server
e.SessionItems["can_subscribe_x"] = true; e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World"; e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
server.InterceptingSubscriptionAsync += e => server.InterceptingSubscriptionAsync += e =>
@@ -262,13 +264,13 @@ namespace MQTTnet.Tests.Server
} }
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
server.InterceptingPublishAsync += e => server.InterceptingPublishAsync += e =>
{ {
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty); e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
string receivedPayload = null; string receivedPayload = null;
@@ -277,7 +279,7 @@ namespace MQTTnet.Tests.Server
client.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
receivedPayload = e.ApplicationMessage.ConvertPayloadToString(); receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var subscribeResult = await client.SubscribeAsync("x"); var subscribeResult = await client.SubscribeAsync("x");
@@ -327,7 +329,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -335,10 +337,10 @@ namespace MQTTnet.Tests.Server
// C2 has the last will defined. // C2 has the last will defined.
var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner"); var clientOptions = new MqttClientOptionsBuilder().WithWillTopic("My/last/will").WithClientId("WillOwner");
var c2 = await testEnvironment.ConnectClient(clientOptions); await testEnvironment.ConnectClient(clientOptions);
// C3 will do the connection takeover. // C3 will do the connection takeover.
var c3 = await testEnvironment.ConnectClient(clientOptions); await testEnvironment.ConnectClient(clientOptions);
await Task.Delay(1000); await Task.Delay(1000);
@@ -350,34 +352,22 @@ namespace MQTTnet.Tests.Server
{ {
try try
{ {
var sendClient = await testEnvironment.ConnectClient(options); var client = await testEnvironment.ConnectClient(options).ConfigureAwait(false);
sendClient.ApplicationMessageReceivedAsync += e => client.ApplicationMessageReceivedAsync += e =>
{ {
onReceive(); onReceive();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
using (var subscribeTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5))) using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{ {
await sendClient.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, subscribeTimeout.Token).ConfigureAwait(false); await client.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, timeout.Token).ConfigureAwait(false);
} }
return sendClient; return client;
} }
catch catch (Exception)
{
return null;
}
}
static async Task<IMqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
return await testEnvironment.ConnectClient(options);
}
catch
{ {
return null; return null;
} }
+28 -12
View File
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
@@ -33,7 +33,7 @@ namespace MQTTnet.Tests.Server
e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid; e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -46,6 +46,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()
{ {
@@ -56,7 +72,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build())); server.InjectApplicationMessage(new InjectedMqttApplicationMessage(new MqttApplicationMessageBuilder().WithTopic("test_topic").Build()));
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var client = await testEnvironment.ConnectClient(); var client = await testEnvironment.ConnectClient();
@@ -81,7 +97,7 @@ namespace MQTTnet.Tests.Server
{ {
// Set the topic to "a" regards what the client wants to subscribe. // Set the topic to "a" regards what the client wants to subscribe.
e.TopicFilter.Topic = "a"; e.TopicFilter.Topic = "a";
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var topicAReceived = false; var topicAReceived = false;
@@ -99,7 +115,7 @@ namespace MQTTnet.Tests.Server
topicBReceived = true; topicBReceived = true;
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await client.SubscribeAsync("b"); await client.SubscribeAsync("b");
@@ -146,7 +162,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
for (var i = 0; i < 500; i++) for (var i = 0; i < 500; i++)
@@ -189,7 +205,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var optionsBuilder = new MqttClientSubscribeOptionsBuilder(); var optionsBuilder = new MqttClientSubscribeOptionsBuilder();
@@ -229,7 +245,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build()); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").Build());
@@ -267,7 +283,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build()); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter("a").WithTopicFilter("b").WithTopicFilter("c").Build());
@@ -301,7 +317,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2")); var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2"));
@@ -316,7 +332,7 @@ namespace MQTTnet.Tests.Server
server.ClientSubscribedTopicAsync += e => server.ClientSubscribedTopicAsync += e =>
{ {
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
@@ -331,7 +347,7 @@ namespace MQTTnet.Tests.Server
server.ClientUnsubscribedTopicAsync += e => server.ClientUnsubscribedTopicAsync += e =>
{ {
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId; unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.UnsubscribeAsync("a"); await c1.UnsubscribeAsync("a");
@@ -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;
} }
} }
} }
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Internal;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
{ {
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests.Server
receivedTopics.Add(e.ApplicationMessage.Topic); receivedTopics.Add(e.ApplicationMessage.Topic);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync("#"); await c1.SubscribeAsync("#");
@@ -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("#");
}
}
}
}
@@ -9,6 +9,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
@@ -48,7 +49,7 @@ namespace MQTTnet.Tests.Server
receiver.ApplicationMessageReceivedAsync += e => receiver.ApplicationMessageReceivedAsync += e =>
{ {
receivedMessage = e.ApplicationMessage; receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await sender.PublishAsync(message.Build(), CancellationToken.None); await sender.PublishAsync(message.Build(), CancellationToken.None);
+3 -3
View File
@@ -2,7 +2,7 @@ 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;
using MQTTnet.Implementations; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server namespace MQTTnet.Tests.Server
@@ -26,7 +26,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
@@ -53,7 +53,7 @@ namespace MQTTnet.Tests.Server
c1.ApplicationMessageReceivedAsync += e => c1.ApplicationMessageReceivedAsync += e =>
{ {
Interlocked.Increment(ref receivedMessagesCount); Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
}; };
await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build());
+1 -2
View File
@@ -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
{ {
+42 -6
View File
@@ -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;
@@ -23,7 +22,7 @@ namespace MQTTnet.Adapter
{ {
const uint ErrorOperationAborted = 0x800703E3; const uint ErrorOperationAborted = 0x800703E3;
const int ReadBufferSize = 4096; const int ReadBufferSize = 4096;
readonly IMqttChannel _channel; readonly IMqttChannel _channel;
readonly byte[] _fixedHeaderBuffer = new byte[2]; readonly byte[] _fixedHeaderBuffer = new byte[2];
readonly MqttNetSourceLogger _logger; readonly MqttNetSourceLogger _logger;
@@ -73,7 +72,26 @@ namespace MQTTnet.Adapter
try try
{ {
await _channel.ConnectAsync(cancellationToken).ConfigureAwait(false); /*
* We have to implement a small workaround here to support connecting in Xamarin
* with a disabled WiFi network. If the WiFi is disabled the connect method will
* block forever. Even a cancellation token is not supported properly.
*/
var connectTask = _channel.ConnectAsync(cancellationToken);
var timeout = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => timeout.TrySetResult(null)))
{
await Task.WhenAny(connectTask, timeout.Task).ConfigureAwait(false);
if (timeout.Task.IsCompleted && !connectTask.IsCompleted)
{
throw new OperationCanceledException("MQTT connect cancelled.", cancellationToken);
}
// Make sure that the exception from the connect task gets thrown.
await connectTask.ConfigureAwait(false);
}
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -176,7 +194,7 @@ namespace MQTTnet.Adapter
// This lock makes sure that multiple threads can send packets at the same time. // This lock makes sure that multiple threads can send packets at the same time.
// This is required when a disconnect is sent from another thread while the // This is required when a disconnect is sent from another thread while the
// worker thread is still sending publish packets etc. // worker thread is still sending publish packets etc.
using (await _syncRoot.WaitAsync(cancellationToken).ConfigureAwait(false)) using (await _syncRoot.EnterAsync(cancellationToken).ConfigureAwait(false))
{ {
// Check for cancellation here again because "WaitAsync" might take some time. // Check for cancellation here again because "WaitAsync" might take some time.
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -277,8 +295,26 @@ 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)
{
return ReadFixedHeaderResult.Cancelled;
}
int bytesRead;
try
{
bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return ReadFixedHeaderResult.Cancelled;
}
catch (SocketException)
{
return ReadFixedHeaderResult.ConnectionClosed;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return ReadFixedHeaderResult.Cancelled; return ReadFixedHeaderResult.Cancelled;
@@ -344,7 +380,7 @@ namespace MQTTnet.Adapter
var fixedHeader = readFixedHeaderResult.FixedHeader; var fixedHeader = readFixedHeaderResult.FixedHeader;
if (fixedHeader.RemainingLength == 0) if (fixedHeader.RemainingLength == 0)
{ {
return new ReceivedMqttPacket(fixedHeader.Flags, PlatformAbstractionLayer.EmptyByteArraySegment, 2); return new ReceivedMqttPacket(fixedHeader.Flags, EmptyBuffer.ArraySegment, 2);
} }
var bodyLength = fixedHeader.RemainingLength; var bodyLength = fixedHeader.RemainingLength;
@@ -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)
{
}
}
}
@@ -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;
+1
View File
@@ -8,6 +8,7 @@ namespace MQTTnet.Client
public interface IMqttClient : IDisposable public interface IMqttClient : IDisposable
{ {
event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync; event Func<MqttApplicationMessageReceivedEventArgs, Task> ApplicationMessageReceivedAsync;
event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync; event Func<MqttClientConnectedEventArgs, Task> ConnectedAsync;
event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync; event Func<MqttClientConnectingEventArgs, Task> ConnectingAsync;
+24 -18
View File
@@ -9,7 +9,6 @@ using MQTTnet.Adapter;
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.PacketDispatcher; using MQTTnet.PacketDispatcher;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -33,25 +32,21 @@ 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 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;
@@ -129,7 +124,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;
@@ -224,17 +219,17 @@ namespace MQTTnet.Client
public async Task PingAsync(CancellationToken cancellationToken = default) public async Task PingAsync(CancellationToken cancellationToken = default)
{ {
if (!cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
}
else
{ {
using (var timeout = new CancellationTokenSource(Options.Timeout)) using (var timeout = new CancellationTokenSource(Options.Timeout))
{ {
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false); await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, timeout.Token).ConfigureAwait(false);
} }
} }
else
{
await SendAndReceiveAsync<MqttPingRespPacket>(MqttPingReqPacket.Instance, cancellationToken).ConfigureAwait(false);
}
} }
public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default) public Task<MqttClientPublishResult> PublishAsync(MqttApplicationMessage applicationMessage, CancellationToken cancellationToken = default)
@@ -369,7 +364,7 @@ namespace MQTTnet.Client
throw new MqttProtocolViolationException("Received a not supported QoS level."); throw new MqttProtocolViolationException("Received a not supported QoS level.");
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken) async Task<MqttClientConnectResult> AuthenticateAsync(MqttClientOptions options, CancellationToken cancellationToken)
@@ -415,6 +410,10 @@ namespace MQTTnet.Client
_publishPacketReceiverQueue = null; _publishPacketReceiverQueue = null;
_adapter?.Dispose(); _adapter?.Dispose();
_adapter = null;
_packetDispatcher?.Dispose();
_packetDispatcher = null;
} }
} }
@@ -448,6 +447,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);
@@ -490,7 +491,7 @@ namespace MQTTnet.Client
return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected); return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
bool DisconnectIsPendingOrFinished() bool DisconnectIsPendingOrFinished()
@@ -550,7 +551,7 @@ namespace MQTTnet.Client
return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this)); return extendedAuthenticationExchangeHandler.HandleRequestAsync(new MqttExtendedAuthenticationExchangeContext(authPacket, this));
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket) Task ProcessReceivedDisconnectPacket(MqttDisconnectPacket disconnectPacket)
@@ -559,7 +560,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);
} }
@@ -604,7 +605,7 @@ namespace MQTTnet.Client
return SendAsync(pubRelPacket, cancellationToken); return SendAsync(pubRelPacket, cancellationToken);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) Task ProcessReceivedPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
@@ -777,7 +778,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);
@@ -828,6 +829,11 @@ namespace MQTTnet.Client
return; return;
} }
if (exception is AggregateException aggregateException)
{
exception = aggregateException.GetBaseException();
}
if (exception is OperationCanceledException) if (exception is OperationCanceledException)
{ {
} }
@@ -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)
{ {
} }
+4 -4
View File
@@ -6,13 +6,13 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Implementations; using MQTTnet.Internal;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public sealed class MqttBufferReader public sealed class MqttBufferReader
{ {
byte[] _buffer = PlatformAbstractionLayer.EmptyByteArray; byte[] _buffer = EmptyBuffer.Array;
int _initialPosition; int _initialPosition;
int _length; int _length;
int _position; int _position;
@@ -29,7 +29,7 @@ namespace MQTTnet.Formatter
if (length == 0) if (length == 0)
{ {
return PlatformAbstractionLayer.EmptyByteArray; return EmptyBuffer.Array;
} }
ValidateReceiveBuffer(length); ValidateReceiveBuffer(length);
@@ -66,7 +66,7 @@ namespace MQTTnet.Formatter
if (bufferLength == 0) if (bufferLength == 0)
{ {
return PlatformAbstractionLayer.EmptyByteArray; return EmptyBuffer.Array;
} }
var buffer = new byte[bufferLength]; var buffer = new byte[bufferLength];
+2 -1
View File
@@ -5,12 +5,13 @@
using System; using System;
using System.Linq; using System.Linq;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
public readonly struct MqttPacketBuffer public readonly struct MqttPacketBuffer
{ {
static readonly ArraySegment<byte> EmptyPayload = PlatformAbstractionLayer.EmptyByteArraySegment; static readonly ArraySegment<byte> EmptyPayload = EmptyBuffer.ArraySegment;
public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload) public MqttPacketBuffer(ArraySegment<byte> packet, ArraySegment<byte> payload)
{ {
@@ -7,6 +7,7 @@ namespace MQTTnet.Formatter
public sealed class MqttPacketFactories public sealed class MqttPacketFactories
{ {
public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory(); public MqttConnAckPacketFactory ConnAck { get; } = new MqttConnAckPacketFactory();
public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory(); public MqttConnectPacketFactory Connect { get; } = new MqttConnectPacketFactory();
public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory(); public MqttDisconnectPacketFactory Disconnect { get; } = new MqttDisconnectPacketFactory();
@@ -5,6 +5,7 @@
using System; using System;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Server;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
@@ -80,5 +81,17 @@ namespace MQTTnet.Formatter
return packet; return packet;
} }
public MqttPublishPacket Create(MqttRetainedMessageMatch retainedMessage)
{
if (retainedMessage == null)
{
throw new ArgumentNullException(nameof(retainedMessage));
}
var publishPacket = Create(retainedMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = retainedMessage.SubscriptionQualityOfServiceLevel;
return publishPacket;
}
} }
} }
@@ -10,16 +10,16 @@ namespace MQTTnet.Formatter
{ {
public sealed class MqttUnsubAckPacketFactory public sealed class MqttUnsubAckPacketFactory
{ {
public MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, MqttUnsubscribeResult mqttUnsubscribeResult) public MqttUnsubAckPacket Create(MqttUnsubscribePacket unsubscribePacket, UnsubscribeResult unsubscribeResult)
{ {
if (unsubscribePacket == null) if (unsubscribePacket == null)
{ {
throw new ArgumentNullException(nameof(unsubscribePacket)); throw new ArgumentNullException(nameof(unsubscribePacket));
} }
if (mqttUnsubscribeResult == null) if (unsubscribeResult == null)
{ {
throw new ArgumentNullException(nameof(mqttUnsubscribeResult)); throw new ArgumentNullException(nameof(unsubscribeResult));
} }
var unsubAckPacket = new MqttUnsubAckPacket var unsubAckPacket = new MqttUnsubAckPacket
@@ -28,7 +28,7 @@ namespace MQTTnet.Formatter
}; };
// MQTTv5.0.0 only. // MQTTv5.0.0 only.
unsubAckPacket.ReasonCodes = mqttUnsubscribeResult.ReasonCodes; unsubAckPacket.ReasonCodes = unsubscribeResult.ReasonCodes;
return unsubAckPacket; return unsubAckPacket;
} }
@@ -15,14 +15,20 @@ namespace MQTTnet.Implementations
public sealed class CrossPlatformSocket : IDisposable public sealed class CrossPlatformSocket : IDisposable
{ {
readonly Socket _socket; readonly Socket _socket;
#if !NET5_0_OR_GREATER
readonly Action _socketDisposeAction; readonly Action _socketDisposeAction;
#endif
NetworkStream _networkStream; NetworkStream _networkStream;
public CrossPlatformSocket(AddressFamily addressFamily) public CrossPlatformSocket(AddressFamily addressFamily)
{ {
_socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
public CrossPlatformSocket() public CrossPlatformSocket()
@@ -31,17 +37,76 @@ namespace MQTTnet.Implementations
// will make use of dual mode in the .net framework. // will make use of dual mode in the .net framework.
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
public CrossPlatformSocket(Socket socket) CrossPlatformSocket(Socket socket)
{ {
_socket = socket ?? throw new ArgumentNullException(nameof(socket)); _socket = socket ?? throw new ArgumentNullException(nameof(socket));
_networkStream = new NetworkStream(socket, true); _networkStream = new NetworkStream(socket, true);
#if !NET5_0_OR_GREATER
_socketDisposeAction = _socket.Dispose; _socketDisposeAction = _socket.Dispose;
#endif
} }
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public bool IsConnected => _socket.Connected;
public bool KeepAlive
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive) as int? == 1;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, value ? 1 : 0);
}
public int TcpKeepAliveInterval
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, value);
#else
get { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveInterval requires at least net5.0."); }
#endif
}
public int TcpKeepAliveRetryCount
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, value);
#else
get { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveRetryCount requires at least net5.0."); }
#endif
}
public int TcpKeepAliveTime
{
#if NETCOREAPP3_0_OR_GREATER
get => _socket.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime) as int? ?? 0;
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, value);
#else
get { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
set { throw new NotSupportedException("TcpKeepAliveTime requires at least net5.0."); }
#endif
}
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public bool NoDelay public bool NoDelay
{ {
// We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6. // We cannot use the _NoDelay_ property from the socket because there is an issue in .NET 4.5.2, 4.6.
@@ -51,24 +116,20 @@ namespace MQTTnet.Implementations
set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0); set => _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, value ? 1 : 0);
} }
public LingerOption LingerState
{
get => _socket.LingerState;
set => _socket.LingerState = value;
}
public bool DualMode
{
get => _socket.DualMode;
set => _socket.DualMode = value;
}
public int ReceiveBufferSize public int ReceiveBufferSize
{ {
get => _socket.ReceiveBufferSize; get => _socket.ReceiveBufferSize;
set => _socket.ReceiveBufferSize = value; set => _socket.ReceiveBufferSize = value;
} }
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => _socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) as int? != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public int SendBufferSize public int SendBufferSize
{ {
get => _socket.SendBufferSize; get => _socket.SendBufferSize;
@@ -81,16 +142,6 @@ namespace MQTTnet.Implementations
set => _socket.SendTimeout = value; set => _socket.SendTimeout = value;
} }
public EndPoint RemoteEndPoint => _socket.RemoteEndPoint;
public bool ReuseAddress
{
get => (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress) != 0;
set => _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, value ? 1 : 0);
}
public bool IsConnected => _socket.Connected;
public async Task<CrossPlatformSocket> AcceptAsync() public async Task<CrossPlatformSocket> AcceptAsync()
{ {
try try
@@ -109,8 +160,6 @@ namespace MQTTnet.Implementations
} }
} }
public EndPoint LocalEndPoint => _socket.LocalEndPoint;
public void Bind(EndPoint localEndPoint) public void Bind(EndPoint localEndPoint)
{ {
if (localEndPoint is null) if (localEndPoint is null)
@@ -121,11 +170,6 @@ namespace MQTTnet.Implementations
_socket.Bind(localEndPoint); _socket.Bind(localEndPoint);
} }
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
}
public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken) public async Task ConnectAsync(string host, int port, CancellationToken cancellationToken)
{ {
if (host is null) if (host is null)
@@ -137,11 +181,17 @@ namespace MQTTnet.Implementations
try try
{ {
_networkStream?.Dispose();
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
if (_networkStream != null)
{
await _networkStream.DisposeAsync().ConfigureAwait(false);
}
await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false); await _socket.ConnectAsync(host, port, cancellationToken).ConfigureAwait(false);
#else #else
_networkStream?.Dispose();
// Workaround for: https://github.com/dotnet/corefx/issues/24430 // Workaround for: https://github.com/dotnet/corefx/issues/24430
using (cancellationToken.Register(_socketDisposeAction)) using (cancellationToken.Register(_socketDisposeAction))
{ {
@@ -175,20 +225,26 @@ namespace MQTTnet.Implementations
} }
} }
public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public void Dispose()
{ {
try _networkStream?.Dispose();
_socket?.Dispose();
}
public NetworkStream GetStream()
{
var networkStream = _networkStream;
if (networkStream == null)
{ {
#if NET452 || NET461 throw new IOException("The socket is not connected.");
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
} }
return networkStream;
}
public void Listen(int connectionBacklog)
{
_socket.Listen(connectionBacklog);
} }
public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags) public async Task<int> ReceiveAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -208,28 +264,27 @@ namespace MQTTnet.Implementations
} }
} }
public NetworkStream GetStream() public async Task SendAsync(ArraySegment<byte> buffer, SocketFlags socketFlags)
{ {
var networkStream = _networkStream; try
if (networkStream == null)
{ {
throw new IOException("The socket is not connected."); #if NET452 || NET461
await Task.Factory.FromAsync(SocketWrapper.BeginSend, _socket.EndSend, new SocketWrapper(_socket, buffer, socketFlags)).ConfigureAwait(false);
#else
await _socket.SendAsync(buffer, socketFlags).ConfigureAwait(false);
#endif
}
catch (ObjectDisposedException)
{
// This will happen when _socket.EndConnect gets called by Task library but the socket is already disposed.
} }
return networkStream;
}
public void Dispose()
{
_networkStream?.Dispose();
_socket?.Dispose();
} }
#if NET452 || NET461 #if NET452 || NET461
sealed class SocketWrapper sealed class SocketWrapper
{ {
readonly Socket _socket;
readonly ArraySegment<byte> _buffer; readonly ArraySegment<byte> _buffer;
readonly Socket _socket;
readonly SocketFlags _socketFlags; readonly SocketFlags _socketFlags;
public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags) public SocketWrapper(Socket socket, ArraySegment<byte> buffer, SocketFlags socketFlags)
@@ -239,16 +294,28 @@ namespace MQTTnet.Implementations
_socketFlags = socketFlags; _socketFlags = socketFlags;
} }
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state);
}
public static IAsyncResult BeginReceive(AsyncCallback callback, object state) public static IAsyncResult BeginReceive(AsyncCallback callback, object state)
{ {
var socketWrapper = (SocketWrapper)state; var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginReceive(socketWrapper._buffer.Array, socketWrapper._buffer.Offset, socketWrapper._buffer.Count, socketWrapper._socketFlags, callback, state); return socketWrapper._socket.BeginReceive(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
}
public static IAsyncResult BeginSend(AsyncCallback callback, object state)
{
var socketWrapper = (SocketWrapper)state;
return socketWrapper._socket.BeginSend(
socketWrapper._buffer.Array,
socketWrapper._buffer.Offset,
socketWrapper._buffer.Count,
socketWrapper._socketFlags,
callback,
state);
} }
} }
#endif #endif
@@ -14,6 +14,7 @@ using System.Runtime.ExceptionServices;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -154,7 +155,7 @@ namespace MQTTnet.Implementations
public Task DisconnectAsync(CancellationToken cancellationToken) public Task DisconnectAsync(CancellationToken cancellationToken)
{ {
Dispose(); Dispose();
return Task.FromResult(0); return CompletedTask.Instance;
} }
public void Dispose() public void Dispose()
@@ -13,6 +13,7 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
namespace MQTTnet.Implementations namespace MQTTnet.Implementations
{ {
@@ -56,13 +57,13 @@ namespace MQTTnet.Implementations
RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token); RegisterListeners(options.TlsEndpointOptions, tlsCertificate, logger, _cancellationTokenSource.Token);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
public Task StopAsync() public Task StopAsync()
{ {
Cleanup(); Cleanup();
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
public void Dispose() public void Dispose()
@@ -124,7 +125,7 @@ namespace MQTTnet.Implementations
var clientHandler = ClientHandler; var clientHandler = ClientHandler;
if (clientHandler == null) if (clientHandler == null)
{ {
return Task.FromResult(0); return CompletedTask.Instance;
} }
return clientHandler(channelAdapter); return clientHandler(channelAdapter);
@@ -117,7 +117,7 @@ namespace MQTTnet.Implementations
return; return;
} }
using (await _sendLock.WaitAsync(cancellationToken).ConfigureAwait(false)) using (await _sendLock.EnterAsync(cancellationToken).ConfigureAwait(false))
{ {
await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false); await _webSocket.SendAsync(buffer, WebSocketMessageType.Binary, isEndOfPacket, cancellationToken).ConfigureAwait(false);
} }
@@ -1,43 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MQTTnet.Implementations
{
public static class PlatformAbstractionLayer
{
#if NET452
public static Task CompletedTask { get; } = Task.FromResult(0);
public static byte[] EmptyByteArray { get; } = new byte[0];
#else
public static Task CompletedTask { get; } = Task.CompletedTask;
public static byte[] EmptyByteArray { get; } = Array.Empty<byte>();
#endif
public static ArraySegment<byte> EmptyByteArraySegment { get; } = new ArraySegment<byte>(EmptyByteArray);
public static void Sleep(TimeSpan timeout)
{
#if !NETSTANDARD1_3 && !WINDOWS_UWP
try
{
System.Threading.Thread.Sleep(timeout);
}
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
}
}
}
+5 -6
View File
@@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
@@ -37,7 +36,7 @@ namespace MQTTnet.Internal
_handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler)); _handlers.Add(new AsyncEventInvocator<TEventArgs>(null, handler));
HasHandlers = true; HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList()); _handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
} }
} }
@@ -53,7 +52,7 @@ namespace MQTTnet.Internal
_handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null)); _handlers.Add(new AsyncEventInvocator<TEventArgs>(handler, null));
HasHandlers = true; HasHandlers = true;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList()); _handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
} }
} }
@@ -86,7 +85,7 @@ namespace MQTTnet.Internal
_handlers.RemoveAll(h => h.WrapsHandler(handler)); _handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0; HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList()); _handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
} }
} }
@@ -102,7 +101,7 @@ namespace MQTTnet.Internal
_handlers.RemoveAll(h => h.WrapsHandler(handler)); _handlers.RemoveAll(h => h.WrapsHandler(handler));
HasHandlers = _handlers.Count > 0; HasHandlers = _handlers.Count > 0;
_handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers.ToList()); _handlersForInvoke = new List<AsyncEventInvocator<TEventArgs>>(_handlers);
} }
} }
@@ -124,7 +123,7 @@ namespace MQTTnet.Internal
} }
catch (Exception exception) catch (Exception exception)
{ {
logger.Warning(exception, $"Error while invoking event ({typeof(TEventArgs)})."); logger.Warning(exception, $"Error while invoking event with arguments of type {typeof(TEventArgs)}.");
} }
} }
} }
@@ -36,7 +36,7 @@ namespace MQTTnet.Internal
if (_handler != null) if (_handler != null)
{ {
_handler.Invoke(eventArgs); _handler.Invoke(eventArgs);
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
if (_asyncHandler != null) if (_asyncHandler != null)
+83 -105
View File
@@ -4,7 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -12,173 +12,151 @@ namespace MQTTnet.Internal
{ {
public sealed class AsyncLock : IDisposable public sealed class AsyncLock : IDisposable
{ {
/* readonly Task<IDisposable> _completedTask;
* This async supporting lock does not support reentrancy! readonly IDisposable _releaser;
*/
readonly List<Releaser> _queuedTasks = new List<Releaser>(64);
readonly Task<IDisposable> _releaserTaskWithDirectApproval;
readonly Releaser _releaserWithDirectApproval;
readonly object _syncRoot = new object(); readonly object _syncRoot = new object();
readonly Queue<AsyncLockWaiter> _waiters = new Queue<AsyncLockWaiter>(64);
volatile bool _isDisposed;
bool _isLocked;
bool _isDisposed;
public AsyncLock() public AsyncLock()
{ {
_releaserWithDirectApproval = new Releaser(this, null, CancellationToken.None); _releaser = new Releaser(this);
_releaserTaskWithDirectApproval = Task.FromResult((IDisposable)_releaserWithDirectApproval); _completedTask = Task.FromResult(_releaser);
} }
public void Dispose() public void Dispose()
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
foreach (var waitingTask in _queuedTasks)
{
waitingTask.Fail(new ObjectDisposedException(nameof(AsyncLock)));
}
_queuedTasks.Clear();
_isDisposed = true; _isDisposed = true;
while (_waiters.Any())
{
_waiters.Dequeue().Dispose();
}
} }
} }
public Task<IDisposable> WaitAsync(CancellationToken cancellationToken) public Task<IDisposable> EnterAsync(CancellationToken cancellationToken = default)
{ {
var hasDirectApproval = false; cancellationToken.ThrowIfCancellationRequested();
Releaser releaser;
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncLock));
}
lock (_syncRoot) lock (_syncRoot)
{ {
if (_isDisposed) if (!_isLocked)
{ {
throw new ObjectDisposedException(nameof(AsyncLock)); _isLocked = true;
} return _completedTask;
if (_queuedTasks.Count == 0)
{
// There is no other waiting task apart from the current one.
// So we can approve the current task directly.
releaser = _releaserWithDirectApproval;
hasDirectApproval = true;
Debug.WriteLine("AsyncLock: Task -1 directly approved.");
}
else
{
releaser = new Releaser(this, new TaskCompletionSource<IDisposable>(), cancellationToken);
} }
_queuedTasks.Add(releaser); var waiter = new AsyncLockWaiter(cancellationToken);
_waiters.Enqueue(waiter);
return waiter.Task;
} }
if (!hasDirectApproval)
{
return releaser.Task;
}
return _releaserTaskWithDirectApproval;
} }
void Release(Releaser releaser) void Release()
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
if (_isDisposed) if (_isDisposed)
{ {
// There is no much left to do! // All waiters have been canceled with a ObjectDisposedException.
// So there is nothing left to do.
return; return;
} }
var activeTask = _queuedTasks[0]; // Assume that there is no waiter left first.
if (!ReferenceEquals(activeTask, releaser)) _isLocked = false;
{
throw new InvalidOperationException("The active task must be the current releaser.");
}
_queuedTasks.RemoveAt(0); // Try to find the next waiter which can be approved.
// Some of them might be canceled already so it is not
while (_queuedTasks.Count > 0) // guaranteed that the very next waiter is the correct one.
while (_waiters.Any())
{ {
var nextTask = _queuedTasks[0]; var waiter = _waiters.Dequeue();
if (!nextTask.IsPending) var isApproved = waiter.Approve(_releaser);
waiter.Dispose();
if (isApproved)
{ {
// Dequeue all canceled or failed tasks. _isLocked = true;
_queuedTasks.RemoveAt(0); return;
continue;
} }
nextTask.Approve();
return;
} }
Debug.WriteLine("AsyncLock: No Task pending.");
} }
} }
sealed class Releaser : IDisposable sealed class AsyncLockWaiter : IDisposable
{ {
readonly AsyncLock _asyncLock; readonly CancellationTokenRegistration _cancellationRegistration;
readonly CancellationToken _cancellationToken; readonly bool _hasCancellationRegistration;
readonly int _id; readonly AsyncTaskCompletionSource<IDisposable> _promise = new AsyncTaskCompletionSource<IDisposable>();
readonly TaskCompletionSource<IDisposable> _promise;
// ReSharper disable once FieldCanBeMadeReadOnly.Local public AsyncLockWaiter(CancellationToken cancellationToken)
CancellationTokenRegistration _cancellationTokenRegistration;
internal Releaser(AsyncLock asyncLock, TaskCompletionSource<IDisposable> promise, CancellationToken cancellationToken)
{ {
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock)); cancellationToken.ThrowIfCancellationRequested();
_promise = promise;
_cancellationToken = cancellationToken;
if (cancellationToken.CanBeCanceled) if (cancellationToken.CanBeCanceled)
{ {
_cancellationTokenRegistration = cancellationToken.Register(Cancel); _cancellationRegistration = cancellationToken.Register(Cancel);
_hasCancellationRegistration = true;
} }
_id = promise?.Task.Id ?? -1;
Debug.WriteLine($"AsyncLock: Task {_id} queued.");
} }
public bool IsPending => _promise != null && !_promise.Task.IsCanceled && !_promise.Task.IsFaulted && !_promise.Task.IsCompleted; public Task<IDisposable> Task => _promise.Task;
public Task<IDisposable> Task => _promise?.Task; public bool Approve(IDisposable scope)
public void Approve()
{ {
_promise?.TrySetResult(this); if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
Debug.WriteLine($"AsyncLock: Task {_id} approved."); if (_promise.Task.IsCompleted)
{
return false;
}
return _promise.TrySetResult(scope);
} }
public void Dispose() public void Dispose()
{ {
if (_cancellationToken.CanBeCanceled) if (_hasCancellationRegistration)
{ {
_cancellationTokenRegistration.Dispose(); _cancellationRegistration.Dispose();
} }
Debug.WriteLine($"AsyncLock: Task {_id} completed."); _promise.TrySetException(new ObjectDisposedException(nameof(AsyncLockWaiter)));
_asyncLock.Release(this);
}
public void Fail(Exception exception)
{
_promise?.TrySetException(exception);
Debug.WriteLine($"AsyncLock: Task {_id} failed ({exception.GetType().Name}).");
} }
void Cancel() void Cancel()
{ {
_promise?.TrySetCanceled(); _promise.TrySetCanceled();
}
}
Debug.WriteLine($"AsyncLock: Task {_id} canceled."); readonly struct Releaser : IDisposable
{
readonly AsyncLock _asyncLock;
public Releaser(AsyncLock asyncLock)
{
_asyncLock = asyncLock ?? throw new ArgumentNullException(nameof(asyncLock));
}
public void Dispose()
{
_asyncLock.Release();
} }
} }
} }
+54 -55
View File
@@ -11,64 +11,39 @@ namespace MQTTnet.Internal
{ {
public sealed class AsyncQueue<TItem> : IDisposable public sealed class AsyncQueue<TItem> : IDisposable
{ {
readonly AsyncSignal _signal = new AsyncSignal();
readonly object _syncRoot = new object(); readonly object _syncRoot = new object();
SemaphoreSlim _semaphore = new SemaphoreSlim(0);
ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>(); ConcurrentQueue<TItem> _queue = new ConcurrentQueue<TItem>();
bool _isDisposed;
public int Count => _queue.Count; public int Count => _queue.Count;
public void Clear()
{
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
}
public void Dispose()
{
lock (_syncRoot)
{
_signal?.Dispose();
_isDisposed = true;
}
}
public void Enqueue(TItem item) public void Enqueue(TItem item)
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
_queue.Enqueue(item); _queue.Enqueue(item);
_semaphore?.Release(); _signal.Set();
} }
} }
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
Task task;
lock (_syncRoot)
{
if (_semaphore == null)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
task = _semaphore.WaitAsync(cancellationToken);
}
await task.ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
if (_queue.TryDequeue(out var item))
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
}
catch (ArgumentNullException)
{
// The semaphore throws this internally sometimes.
return new AsyncQueueDequeueResult<TItem>(false, default);
}
catch (OperationCanceledException)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
}
return new AsyncQueueDequeueResult<TItem>(false, default);
}
public AsyncQueueDequeueResult<TItem> TryDequeue() public AsyncQueueDequeueResult<TItem> TryDequeue()
{ {
if (_queue.TryDequeue(out var item)) if (_queue.TryDequeue(out var item))
@@ -79,18 +54,42 @@ namespace MQTTnet.Internal
return new AsyncQueueDequeueResult<TItem>(false, default); return new AsyncQueueDequeueResult<TItem>(false, default);
} }
public void Clear() public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
{ {
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>()); while (!cancellationToken.IsCancellationRequested)
}
public void Dispose()
{
lock (_syncRoot)
{ {
_semaphore?.Dispose(); try
_semaphore = null; {
Task task;
lock (_syncRoot)
{
if (_isDisposed)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
task = _signal.WaitAsync(cancellationToken);
}
await task.ConfigureAwait(false);
if (cancellationToken.IsCancellationRequested)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
if (_queue.TryDequeue(out var item))
{
return new AsyncQueueDequeueResult<TItem>(true, item);
}
}
catch (OperationCanceledException)
{
return new AsyncQueueDequeueResult<TItem>(false, default);
}
} }
return new AsyncQueueDequeueResult<TItem>(false, default);
} }
} }
} }
+113
View File
@@ -0,0 +1,113 @@
// 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;
using System.Threading.Tasks;
namespace MQTTnet.Internal
{
public sealed class AsyncSignal : IDisposable
{
readonly object _syncRoot = new object();
bool _isDisposed;
bool _isSignaled;
AsyncSignalWaiter _waiter;
public void Dispose()
{
lock (_syncRoot)
{
_waiter?.Dispose();
_waiter = null;
_isDisposed = true;
}
}
public void Set()
{
lock (_syncRoot)
{
_isSignaled = true;
// If there is already a waiting task let it run.
_waiter?.Approve();
_waiter = null;
}
}
public Task WaitAsync(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
lock (_syncRoot)
{
ThrowIfDisposed();
if (_isSignaled)
{
return CompletedTask.Instance;
}
if (_waiter != null)
{
if (!_waiter.Task.IsCompleted)
{
throw new InvalidOperationException("Only one waiting task is permitted per async signal.");
}
_waiter.Dispose();
}
_waiter = new AsyncSignalWaiter(cancellationToken);
return _waiter.Task;
}
}
void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(AsyncSignal));
}
}
sealed class AsyncSignalWaiter : IDisposable
{
readonly AsyncTaskCompletionSource<bool> _promise = new AsyncTaskCompletionSource<bool>();
// ReSharper disable once FieldCanBeMadeReadOnly.Local
CancellationTokenRegistration _cancellationTokenRegistration;
public AsyncSignalWaiter(CancellationToken cancellationToken)
{
if (cancellationToken.CanBeCanceled)
{
_cancellationTokenRegistration = cancellationToken.Register(Cancel);
}
}
public Task Task => _promise.Task;
public void Approve()
{
_promise.TrySetResult(true);
}
public void Dispose()
{
_cancellationTokenRegistration.Dispose();
_promise.TrySetException(new ObjectDisposedException(nameof(AsyncSignalWaiter)));
}
void Cancel()
{
_promise.TrySetCanceled();
}
}
}
}
@@ -3,6 +3,7 @@
// 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.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MQTTnet.Internal namespace MQTTnet.Internal
@@ -22,7 +23,7 @@ namespace MQTTnet.Internal
public Task<TResult> Task => _taskCompletionSource.Task; public Task<TResult> Task => _taskCompletionSource.Task;
public bool TrySetCanceled() public void TrySetCanceled()
{ {
#if NET452 #if NET452
// To prevent deadlocks it is required to call the _TrySetCanceled_ method // To prevent deadlocks it is required to call the _TrySetCanceled_ method
@@ -31,19 +32,14 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetCanceled()); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetCanceled());
return true; SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
#else #else
return _taskCompletionSource.TrySetCanceled(); _taskCompletionSource.TrySetCanceled();
#endif #endif
} }
public bool TrySetException(Exception exception) public void TrySetException(Exception exception)
{ {
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
#if NET452 #if NET452
// To prevent deadlocks it is required to call the _TrySetException_ method // To prevent deadlocks it is required to call the _TrySetException_ method
// from a new thread because the awaiting code will not(!) be executed in // from a new thread because the awaiting code will not(!) be executed in
@@ -51,9 +47,9 @@ namespace MQTTnet.Internal
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetException(exception)); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetException(exception));
return true; SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
#else #else
return _taskCompletionSource.TrySetException(exception); _taskCompletionSource.TrySetException(exception);
#endif #endif
} }
@@ -65,7 +61,14 @@ namespace MQTTnet.Internal
// a new thread automatically (due to await). Furthermore _this_ thread will // a new thread automatically (due to await). Furthermore _this_ thread will
// do it. But _this_ thread is also reading incoming packets -> deadlock. // do it. But _this_ thread is also reading incoming packets -> deadlock.
// NET452 does not support RunContinuationsAsynchronously // NET452 does not support RunContinuationsAsynchronously
if (_taskCompletionSource.Task.IsCompleted)
{
return false;
}
System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetResult(result)); System.Threading.Tasks.Task.Run(() => _taskCompletionSource.TrySetResult(result));
SpinWait.SpinUntil(() => _taskCompletionSource.Task.IsCompleted);
return true; return true;
#else #else
return _taskCompletionSource.TrySetResult(result); return _taskCompletionSource.TrySetResult(result);
+17
View File
@@ -0,0 +1,17 @@
// 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;
namespace MQTTnet.Internal
{
public static class CompletedTask
{
#if NET452
public static readonly Task Instance = Task.FromResult(true);
#else
public static readonly Task Instance = Task.CompletedTask;
#endif
}
}
+19
View File
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace MQTTnet.Internal
{
public static class EmptyBuffer
{
#if NET452
public static readonly byte[] Array = new byte[0];
#else
public static readonly byte[] Array = System.Array.Empty<byte>();
#endif
public static readonly ArraySegment<byte> ArraySegment = new ArraySegment<byte>(Array, 0, 0);
}
}
+13 -8
View File
@@ -20,7 +20,7 @@ namespace MQTTnet.Internal
new LinkedList<MqttPacketBusItem>() new LinkedList<MqttPacketBusItem>()
}; };
readonly SemaphoreSlim _semaphore = new SemaphoreSlim(0, 1); readonly AsyncSignal _signal = new AsyncSignal();
readonly object _syncRoot = new object(); readonly object _syncRoot = new object();
int _activePartition = (int)MqttPacketBusPartition.Health; int _activePartition = (int)MqttPacketBusPartition.Health;
@@ -82,7 +82,16 @@ namespace MQTTnet.Internal
// No partition contains data so that we have to wait and put // No partition contains data so that we have to wait and put
// the worker back to the thread pool. // the worker back to the thread pool.
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false); try
{
await _signal.WaitAsync(cancellationToken).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
// The cancelled token should "hide" the disposal of the signal.
cancellationToken.ThrowIfCancellationRequested();
throw;
}
} }
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@@ -92,7 +101,7 @@ namespace MQTTnet.Internal
public void Dispose() public void Dispose()
{ {
_semaphore?.Dispose(); _signal.Dispose();
} }
public void DropFirstItem(MqttPacketBusPartition partition) public void DropFirstItem(MqttPacketBusPartition partition)
@@ -118,11 +127,7 @@ namespace MQTTnet.Internal
lock (_syncRoot) lock (_syncRoot)
{ {
_partitions[(int)partition].AddLast(item); _partitions[(int)partition].AddLast(item);
_signal.Set();
if (_semaphore.CurrentCount == 0)
{
_semaphore.Release();
}
} }
} }
-1
View File
@@ -14,7 +14,6 @@
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net452;net461</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks> <TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' AND '$(MSBuildRuntimeType)' != 'Core' ">$(TargetFrameworks);uap10.0</TargetFrameworks>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
<AssemblyName>MQTTnet</AssemblyName> <AssemblyName>MQTTnet</AssemblyName>
<RootNamespace>MQTTnet</RootNamespace> <RootNamespace>MQTTnet</RootNamespace>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
@@ -2,6 +2,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cconnecting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdiagnostics/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cdisconnecting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cexceptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cextendedauthenticationexchange/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cextendedauthenticationexchange/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Coptions/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Coptions/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cpublishing/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=client_005Cpublishing/@EntryIndexedValue">True</s:Boolean>
@@ -2,48 +2,103 @@
// 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.Packets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using MQTTnet.Packets;
namespace MQTTnet.PacketDispatcher namespace MQTTnet.PacketDispatcher
{ {
public sealed class MqttPacketDispatcher public sealed class MqttPacketDispatcher : IDisposable
{ {
readonly List<IMqttPacketAwaitable> _awaitables = new List<IMqttPacketAwaitable>(); readonly List<IMqttPacketAwaitable> _waiters = new List<IMqttPacketAwaitable>();
public void FailAll(Exception exception) bool _isDisposed;
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
{ {
if (exception == null) throw new ArgumentNullException(nameof(exception)); var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
lock (_awaitables) lock (_waiters)
{ {
foreach (var awaitable in _awaitables) _waiters.Add(awaitable);
{
awaitable.Fail(exception);
}
_awaitables.Clear();
} }
return awaitable;
} }
public void CancelAll() public void CancelAll()
{ {
lock (_awaitables) lock (_waiters)
{ {
foreach (var awaitable in _awaitables) foreach (var awaitable in _waiters)
{ {
awaitable.Cancel(); awaitable.Cancel();
} }
_awaitables.Clear(); _waiters.Clear();
} }
} }
public void Dispose()
{
Dispose(new ObjectDisposedException(nameof(MqttPacketDispatcher)));
}
public void Dispose(Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
lock (_waiters)
{
FailAll(exception);
// Make sure that no task can start waiting after this instance is already disposed.
// This will prevent unexpected freezes.
_isDisposed = true;
}
}
public void FailAll(Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
lock (_waiters)
{
foreach (var awaitable in _waiters)
{
awaitable.Fail(exception);
}
_waiters.Clear();
}
}
public void RemoveAwaitable(IMqttPacketAwaitable awaitable)
{
if (awaitable == null)
{
throw new ArgumentNullException(nameof(awaitable));
}
lock (_waiters)
{
_waiters.Remove(awaitable);
}
}
public bool TryDispatch(MqttPacket packet) public bool TryDispatch(MqttPacket packet)
{ {
if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packet == null)
{
throw new ArgumentNullException(nameof(packet));
}
ushort identifier = 0; ushort identifier = 0;
if (packet is MqttPacketWithIdentifier packetWithIdentifier) if (packet is MqttPacketWithIdentifier packetWithIdentifier)
{ {
@@ -51,13 +106,15 @@ namespace MQTTnet.PacketDispatcher
} }
var packetType = packet.GetType(); var packetType = packet.GetType();
var awaitables = new List<IMqttPacketAwaitable>(); var waiters = new List<IMqttPacketAwaitable>();
lock (_awaitables) lock (_waiters)
{ {
for (var i = _awaitables.Count - 1; i >= 0; i--) ThrowIfDisposed();
for (var i = _waiters.Count - 1; i >= 0; i--)
{ {
var entry = _awaitables[i]; var entry = _waiters[i];
// Note: The PingRespPacket will also arrive here and has NO identifier but there // Note: The PingRespPacket will also arrive here and has NO identifier but there
// is code which waits for it. So the code must be able to deal with filters which // is code which waits for it. So the code must be able to deal with filters which
@@ -66,39 +123,25 @@ namespace MQTTnet.PacketDispatcher
{ {
continue; continue;
} }
awaitables.Add(entry); waiters.Add(entry);
_awaitables.RemoveAt(i); _waiters.RemoveAt(i);
} }
} }
foreach (var matchingEntry in awaitables) foreach (var matchingEntry in waiters)
{ {
matchingEntry.Complete(packet); matchingEntry.Complete(packet);
} }
return awaitables.Count > 0; return waiters.Count > 0;
}
public MqttPacketAwaitable<TResponsePacket> AddAwaitable<TResponsePacket>(ushort packetIdentifier) where TResponsePacket : MqttPacket
{
var awaitable = new MqttPacketAwaitable<TResponsePacket>(packetIdentifier, this);
lock (_awaitables)
{
_awaitables.Add(awaitable);
}
return awaitable;
} }
public void RemoveAwaitable(IMqttPacketAwaitable awaitable) void ThrowIfDisposed()
{ {
if (awaitable == null) throw new ArgumentNullException(nameof(awaitable)); if (_isDisposed)
lock (_awaitables)
{ {
_awaitables.Remove(awaitable); throw new ObjectDisposedException(nameof(MqttPacketDispatcher));
} }
} }
} }
@@ -10,15 +10,23 @@ namespace MQTTnet.Server
{ {
public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs public sealed class ClientAcknowledgedPublishPacketEventArgs : EventArgs
{ {
public ClientAcknowledgedPublishPacketEventArgs(string clientId, IDictionary sessionItems, MqttPublishPacket publishPacket, MqttPacketWithIdentifier acknowledgePacket)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
SessionItems = sessionItems ?? throw new ArgumentNullException(nameof(sessionItems));
PublishPacket = publishPacket ?? throw new ArgumentNullException(nameof(publishPacket));
AcknowledgePacket = acknowledgePacket ?? throw new ArgumentNullException(nameof(acknowledgePacket));
}
/// <summary> /// <summary>
/// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet. /// Gets the packet which was used for acknowledge. This can be a PubAck or PubComp packet.
/// </summary> /// </summary>
public MqttPacketWithIdentifier AcknowledgePacket { get; internal set; } public MqttPacketWithIdentifier AcknowledgePacket { get; }
/// <summary> /// <summary>
/// Gets the ID of the client which acknowledged a PUBLISH packet. /// Gets the ID of the client which acknowledged a PUBLISH packet.
/// </summary> /// </summary>
public string ClientId { get; internal set; } public string ClientId { get; }
/// <summary> /// <summary>
/// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2. /// Gets whether the PUBLISH packet is fully acknowledged. This is the case for PUBACK (QoS 1) and PUBCOMP (QoS 2.
@@ -28,11 +36,11 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets the PUBLISH packet which was acknowledged. /// Gets the PUBLISH packet which was acknowledged.
/// </summary> /// </summary>
public MqttPublishPacket PublishPacket { get; internal set; } public MqttPublishPacket PublishPacket { get; }
/// <summary> /// <summary>
/// Gets the session items which contain custom user data per session. /// Gets the session items which contain custom user data per session.
/// </summary> /// </summary>
public IDictionary SessionItems { get; internal set; } public IDictionary SessionItems { get; }
} }
} }
@@ -10,6 +10,7 @@ using System.Text;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -88,7 +89,7 @@ namespace MQTTnet.Server
/// </summary> /// </summary>
public uint MaximumPacketSize => _connectPacket.MaximumPacketSize; public uint MaximumPacketSize => _connectPacket.MaximumPacketSize;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray); public string Password => Encoding.UTF8.GetString(RawPassword ?? EmptyBuffer.Array);
public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion; public MqttProtocolVersion ProtocolVersion => ChannelAdapter.PacketFormatterAdapter.ProtocolVersion;
+32 -37
View File
@@ -11,7 +11,6 @@ using MQTTnet.Client;
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.PacketDispatcher; using MQTTnet.PacketDispatcher;
using MQTTnet.Packets; using MQTTnet.Packets;
@@ -34,6 +33,7 @@ namespace MQTTnet.Server
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>(); readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
CancellationTokenSource _cancellationToken; CancellationTokenSource _cancellationToken;
bool _disconnectPacketSent;
public MqttClient( public MqttClient(
MqttConnectPacket connectPacket, MqttConnectPacket connectPacket,
@@ -147,19 +147,22 @@ namespace MQTTnet.Server
{ {
IsRunning = false; IsRunning = false;
// Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+. if (!_disconnectPacketSent)
if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{ {
// The Client or Server MAY send a DISCONNECT packet before closing the Network Connection. // Sending DISCONNECT packets from the server to the client is only supported when using MQTTv5+.
// This library does not sent a DISCONNECT packet for a normal disconnection. Maybe adding if (ChannelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
// a configuration option is requested in the future.
if (reason != MqttDisconnectReasonCode.NormalDisconnection)
{ {
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the // The Client or Server MAY send a DISCONNECT packet before closing the Network Connection.
// token because the entire connection is closed (disposed) as soon as the cancellation // This library does not sent a DISCONNECT packet for a normal disconnection. Maybe adding
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive // a configuration option is requested in the future.
// at the client! if (reason != MqttDisconnectReasonCode.NormalDisconnection)
await TrySendDisconnectPacket(reason).ConfigureAwait(false); {
// Is is very important to send the DISCONNECT packet here BEFORE cancelling the
// token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client!
await TrySendDisconnectPacket(reason).ConfigureAwait(false);
}
} }
} }
@@ -170,18 +173,11 @@ namespace MQTTnet.Server
{ {
if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers) if (_eventContainer.ClientAcknowledgedPublishPacketEvent.HasHandlers)
{ {
var eventArgs = new ClientAcknowledgedPublishPacketEventArgs var eventArgs = new ClientAcknowledgedPublishPacketEventArgs(Id, Session.Items, publishPacket, acknowledgePacket);
{
PublishPacket = publishPacket,
AcknowledgePacket = acknowledgePacket,
ClientId = Id,
SessionItems = Session.Items
};
return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger); return _eventContainer.ClientAcknowledgedPublishPacketEvent.TryInvokeAsync(eventArgs, _logger);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket) Task HandleIncomingPubAckPacket(MqttPubAckPacket pubAckPacket)
@@ -193,7 +189,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket); return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubAckPacket);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket) Task HandleIncomingPubCompPacket(MqttPubCompPacket pubCompPacket)
@@ -205,7 +201,7 @@ namespace MQTTnet.Server
return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket); return ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubCompPacket);
} }
return PlatformAbstractionLayer.CompletedTask; return CompletedTask.Instance;
} }
async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken) async Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
@@ -272,17 +268,14 @@ namespace MQTTnet.Server
} }
} }
async Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket) Task HandleIncomingPubRecPacket(MqttPubRecPacket pubRecPacket)
{ {
var acknowledgedPublishPacket = Session.PeekAcknowledgePublishPacket(pubRecPacket.PacketIdentifier); // Do not fire the event _ClientAcknowledgedPublishPacket_ here because the QoS 2 process is only finished
// properly when the client has sent the PUBCOMP packet.
if (acknowledgedPublishPacket != null)
{
await ClientAcknowledgedPublishPacket(acknowledgedPublishPacket, pubRecPacket).ConfigureAwait(false);
}
var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubRelPacket = _packetFactories.PubRel.Create(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket)); Session.EnqueueControlPacket(new MqttPacketBusItem(pubRelPacket));
return CompletedTask.Instance;
} }
void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket) void HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket)
@@ -293,7 +286,7 @@ namespace MQTTnet.Server
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{ {
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false); var subscribeResult = await Session.Subscribe(subscribePacket, cancellationToken).ConfigureAwait(false);
var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult); var subAckPacket = _packetFactories.SubAck.Create(subscribePacket, subscribeResult);
@@ -307,18 +300,17 @@ namespace MQTTnet.Server
if (subscribeResult.RetainedMessages != null) if (subscribeResult.RetainedMessages != null)
{ {
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages) foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{ {
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage); var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); Session.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
} }
} }
} }
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{ {
var unsubscribeResult = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false); var unsubscribeResult = await Session.Unsubscribe(unsubscribePacket, cancellationToken).ConfigureAwait(false);
var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult); var unsubAckPacket = _packetFactories.UnsubAck.Create(unsubscribePacket, unsubscribeResult);
@@ -496,7 +488,7 @@ namespace MQTTnet.Server
try try
{ {
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested && !IsTakenOver && IsRunning)
{ {
packetBusItem = await Session.DequeuePacketAsync(cancellationToken).ConfigureAwait(false); packetBusItem = await Session.DequeuePacketAsync(cancellationToken).ConfigureAwait(false);
@@ -578,6 +570,9 @@ namespace MQTTnet.Server
{ {
try try
{ {
// This also indicates that it was tried at least!
_disconnectPacketSent = true;
var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode); var disconnectPacket = _packetFactories.Disconnect.Create(reasonCode);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout)) using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
@@ -150,11 +150,17 @@ namespace MQTTnet.Server
foreach (var session in subscriberSessions) foreach (var session in subscriberSessions)
{ {
var checkSubscriptionsResult = session.SubscriptionsManager.CheckSubscriptions( if(!session.TryCheckSubscriptions(
applicationMessage.Topic, applicationMessage.Topic,
topicHash, topicHash,
applicationMessage.QualityOfServiceLevel, applicationMessage.QualityOfServiceLevel,
senderId); senderId,
out var checkSubscriptionsResult))
{
// Checking the subscriptions has failed for the session. The session
// will be ignored.
continue;
}
if (!checkSubscriptionsResult.IsSubscribed) if (!checkSubscriptionsResult.IsSubscribed)
{ {
@@ -301,11 +307,14 @@ namespace MQTTnet.Server
channelAdapter.Endpoint, channelAdapter.Endpoint,
client.Session.Items); client.Session.Items);
await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _eventContainer.ClientConnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
} }
await client.RunAsync().ConfigureAwait(false); await client.RunAsync().ConfigureAwait(false);
} }
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
} }
@@ -411,13 +420,13 @@ namespace MQTTnet.Server
var clientSession = GetClientSession(clientId); var clientSession = GetClientSession(clientId);
var subscribeResult = await clientSession.SubscriptionsManager.Subscribe(fakeSubscribePacket, CancellationToken.None).ConfigureAwait(false); var subscribeResult = await clientSession.Subscribe(fakeSubscribePacket, CancellationToken.None).ConfigureAwait(false);
if (subscribeResult.RetainedMessages != null) if (subscribeResult.RetainedMessages != null)
{ {
foreach (var retainedApplicationMessage in subscribeResult.RetainedMessages) foreach (var retainedMessageMatch in subscribeResult.RetainedMessages)
{ {
var publishPacket = _packetFactories.Publish.Create(retainedApplicationMessage.ApplicationMessage); var publishPacket = _packetFactories.Publish.Create(retainedMessageMatch);
clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket)); clientSession.EnqueueDataPacket(new MqttPacketBusItem(publishPacket));
} }
} }
@@ -438,7 +447,7 @@ namespace MQTTnet.Server
var fakeUnsubscribePacket = new MqttUnsubscribePacket(); var fakeUnsubscribePacket = new MqttUnsubscribePacket();
fakeUnsubscribePacket.TopicFilters.AddRange(topicFilters); fakeUnsubscribePacket.TopicFilters.AddRange(topicFilters);
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket, CancellationToken.None); return GetClientSession(clientId).Unsubscribe(fakeUnsubscribePacket, CancellationToken.None);
} }
MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session) MqttClient CreateClient(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttSession session)
@@ -479,12 +488,17 @@ namespace MQTTnet.Server
sessionShouldPersist = !connectPacket.CleanSession; sessionShouldPersist = !connectPacket.CleanSession;
} }
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _createConnectionSyncRoot.EnterAsync().ConfigureAwait(false))
{ {
MqttSession session; MqttSession oldSession;
MqttClient oldClient;
lock (_sessionsManagementLock) lock (_sessionsManagementLock)
{ {
if (!_sessions.TryGetValue(connectPacket.ClientId, out session)) MqttSession session;
// Create a new session (if required).
if (!_sessions.TryGetValue(connectPacket.ClientId, out oldSession))
{ {
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist); session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
} }
@@ -493,11 +507,15 @@ namespace MQTTnet.Server
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
{ {
_logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId); _logger.Verbose("Deleting existing session of client '{0}' due to clean start.", connectPacket.ClientId);
_subscriberSessions.Remove(oldSession);
session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist); session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist);
} }
else else
{ {
_logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId);
session = oldSession;
oldSession = null;
// Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval // Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval
session.IsPersistent = sessionShouldPersist; session.IsPersistent = sessionShouldPersist;
connAckPacket.IsSessionPresent = true; connAckPacket.IsSessionPresent = true;
@@ -506,40 +524,45 @@ namespace MQTTnet.Server
} }
_sessions[connectPacket.ClientId] = session; _sessions[connectPacket.ClientId] = session;
// Create a new client (always required).
_clients.TryGetValue(connectPacket.ClientId, out oldClient);
if (oldClient != null)
{
// This will stop the current client from sending and receiving but remains the connection active
// for a later DISCONNECT packet.
oldClient.IsTakenOver = true;
}
client = CreateClient(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client;
} }
if (!connAckPacket.IsSessionPresent) if (!connAckPacket.IsSessionPresent)
{ {
// TODO: This event is not yet final. It can already be used but restoring sessions from storage will be added later! // TODO: This event is not yet final. It can already be used but restoring sessions from storage will be added later!
var preparingSessionEventArgs = new PreparingSessionEventArgs(); var preparingSessionEventArgs = new PreparingSessionEventArgs();
await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false); await _eventContainer.PreparingSessionEvent.TryInvokeAsync(preparingSessionEventArgs, _logger).ConfigureAwait(false);
} }
MqttClient existingClient; if (oldClient != null)
lock (_clients)
{ {
_clients.TryGetValue(connectPacket.ClientId, out existingClient); await oldClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
client = CreateClient(connectPacket, channelAdapter, session);
_clients[connectPacket.ClientId] = client; if (_eventContainer.ClientDisconnectedEvent.HasHandlers)
}
if (existingClient != null)
{
existingClient.IsTakenOver = true;
await existingClient.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false);
if (_eventContainer.ClientConnectedEvent.HasHandlers)
{ {
var eventArgs = new ClientDisconnectedEventArgs( var eventArgs = new ClientDisconnectedEventArgs(
existingClient.Id, oldClient.Id,
MqttClientDisconnectType.Takeover, MqttClientDisconnectType.Takeover,
existingClient.Endpoint, oldClient.Endpoint,
existingClient.Session.Items); oldClient.Session.Items);
await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
await _eventContainer.ClientDisconnectedEvent.TryInvokeAsync(eventArgs, _logger).ConfigureAwait(false);
} }
} }
oldSession?.Dispose();
} }
return client; return client;
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -30,7 +31,7 @@ namespace MQTTnet.Server
readonly Dictionary<string, MqttSubscription> _subscriptions = new Dictionary<string, MqttSubscription>(); readonly Dictionary<string, MqttSubscription> _subscriptions = new Dictionary<string, MqttSubscription>();
// Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries // Use subscription lock to maintain consistency across subscriptions and topic hash dictionaries
readonly SemaphoreSlim _subscriptionsLock = new SemaphoreSlim(1); readonly AsyncLock _subscriptionsLock = new AsyncLock();
readonly Dictionary<ulong, TopicHashMaskSubscriptions> _wildcardSubscriptionsByTopicHash = new Dictionary<ulong, TopicHashMaskSubscriptions>(); readonly Dictionary<ulong, TopicHashMaskSubscriptions> _wildcardSubscriptionsByTopicHash = new Dictionary<ulong, TopicHashMaskSubscriptions>();
public MqttClientSubscriptionsManager( public MqttClientSubscriptionsManager(
@@ -45,13 +46,12 @@ namespace MQTTnet.Server
_subscriptionChangedNotification = subscriptionChangedNotification; _subscriptionChangedNotification = subscriptionChangedNotification;
} }
public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId) public CheckSubscriptionsResult CheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId)
{ {
var possibleSubscriptions = new List<MqttSubscription>(); var possibleSubscriptions = new List<MqttSubscription>();
// Check for possible subscriptions. They might have collisions but this is fine. // Check for possible subscriptions. They might have collisions but this is fine.
_subscriptionsLock.Wait(); using (_subscriptionsLock.EnterAsync(CancellationToken.None).GetAwaiter().GetResult())
try
{ {
if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions)) if (_noWildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var noWildcardSubscriptions))
{ {
@@ -63,18 +63,14 @@ namespace MQTTnet.Server
var wildcardSubscriptions = wcs.Value; var wildcardSubscriptions = wcs.Value;
var subscriptionHash = wcs.Key; var subscriptionHash = wcs.Key;
var subscriptionHashMask = wildcardSubscriptions.HashMask; var subscriptionHashMask = wildcardSubscriptions.HashMask;
if ((topicHash & subscriptionHashMask) == subscriptionHash) if ((topicHash & subscriptionHashMask) == subscriptionHash)
{ {
possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList()); possibleSubscriptions.AddRange(wildcardSubscriptions.Subscriptions.ToList());
} }
} }
} }
finally
{
_subscriptionsLock.Release();
}
// The pre check has evaluated that nothing is subscribed. // The pre check has evaluated that nothing is subscribed.
// If there were some possible candidates they get checked below // If there were some possible candidates they get checked below
// again to avoid collisions. // again to avoid collisions.
@@ -83,7 +79,7 @@ namespace MQTTnet.Server
return CheckSubscriptionsResult.NotSubscribed; return CheckSubscriptionsResult.NotSubscribed;
} }
var senderIsReceiver = string.Equals(senderClientId, _session.Id); var senderIsReceiver = string.Equals(senderId, _session.Id);
var maxQoSLevel = -1; // Not subscribed. var maxQoSLevel = -1; // Not subscribed.
HashSet<uint> subscriptionIdentifiers = null; HashSet<uint> subscriptionIdentifiers = null;
@@ -136,7 +132,7 @@ namespace MQTTnet.Server
SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers, SubscriptionIdentifiers = subscriptionIdentifiers?.ToList() ?? EmptySubscriptionIdentifiers,
// Start with the same QoS as the publisher. // Start with the same QoS as the publisher.
QualityOfServiceLevel = applicationMessageQoSLevel QualityOfServiceLevel = qualityOfServiceLevel
}; };
// Now downgrade if required. // Now downgrade if required.
@@ -148,7 +144,7 @@ namespace MQTTnet.Server
// Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published". // Subscribing to a Topic Filter at QoS 2 is equivalent to saying "I would like to receive Messages matching this filter at the QoS with which they were published".
// This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server // This means a publisher is responsible for determining the maximum QoS a Message can be delivered at, but a subscriber is able to require that the Server
// downgrades the QoS to one more suitable for its usage. // downgrades the QoS to one more suitable for its usage.
if (maxQoSLevel < (int)applicationMessageQoSLevel) if (maxQoSLevel < (int)qualityOfServiceLevel)
{ {
result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel; result.QualityOfServiceLevel = (MqttQualityOfServiceLevel)maxQoSLevel;
} }
@@ -169,10 +165,7 @@ namespace MQTTnet.Server
} }
var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false); var retainedApplicationMessages = await _retainedMessagesManager.GetMessages().ConfigureAwait(false);
var result = new SubscribeResult var result = new SubscribeResult(subscribePacket.TopicFilters.Count);
{
ReasonCodes = new List<MqttSubscribeReasonCode>(subscribePacket.TopicFilters.Count)
};
var addedSubscriptions = new List<string>(); var addedSubscriptions = new List<string>();
var finalTopicFilters = new List<MqttTopicFilter>(); var finalTopicFilters = new List<MqttTopicFilter>();
@@ -212,7 +205,7 @@ namespace MQTTnet.Server
// This call will add the new subscription to the internal storage. // This call will add the new subscription to the internal storage.
// So the event _ClientSubscribedTopicEvent_ must be called afterwards. // So the event _ClientSubscribedTopicEvent_ must be called afterwards.
_subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions); _subscriptionChangedNotification?.OnSubscriptionsAdded(_session, addedSubscriptions);
if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers) if (_eventContainer.ClientSubscribedTopicEvent.HasHandlers)
{ {
foreach (var finalTopicFilter in finalTopicFilters) foreach (var finalTopicFilter in finalTopicFilters)
@@ -221,23 +214,22 @@ namespace MQTTnet.Server
await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _eventContainer.ClientSubscribedTopicEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
} }
} }
return result; return result;
} }
public async Task<MqttUnsubscribeResult> Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) public async Task<UnsubscribeResult> Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{ {
if (unsubscribePacket == null) if (unsubscribePacket == null)
{ {
throw new ArgumentNullException(nameof(unsubscribePacket)); throw new ArgumentNullException(nameof(unsubscribePacket));
} }
var result = new MqttUnsubscribeResult(); var result = new UnsubscribeResult();
var removedSubscriptions = new List<string>(); var removedSubscriptions = new List<string>();
await _subscriptionsLock.WaitAsync(cancellationToken).ConfigureAwait(false); using (await _subscriptionsLock.EnterAsync(cancellationToken).ConfigureAwait(false))
try
{ {
foreach (var topicFilter in unsubscribePacket.TopicFilters) foreach (var topicFilter in unsubscribePacket.TopicFilters)
{ {
@@ -268,7 +260,7 @@ namespace MQTTnet.Server
if (existingSubscription != null) if (existingSubscription != null)
{ {
var topicHash = existingSubscription.TopicHash; var topicHash = existingSubscription.TopicHash;
if (existingSubscription.TopicHasWildcard) if (existingSubscription.TopicHasWildcard)
{ {
if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions)) if (_wildcardSubscriptionsByTopicHash.TryGetValue(topicHash, out var subscriptions))
@@ -297,11 +289,8 @@ namespace MQTTnet.Server
} }
} }
} }
finally
{ _subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
_subscriptionsLock.Release();
_subscriptionChangedNotification?.OnSubscriptionsRemoved(_session, removedSubscriptions);
}
if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers) if (_eventContainer.ClientUnsubscribedTopicEvent.HasHandlers)
{ {
@@ -348,8 +337,7 @@ namespace MQTTnet.Server
// Add to subscriptions and maintain topic hash dictionaries // Add to subscriptions and maintain topic hash dictionaries
_subscriptionsLock.Wait(); using (_subscriptionsLock.EnterAsync(CancellationToken.None).GetAwaiter().GetResult())
try
{ {
MqttSubscription.CalculateTopicHash(topicFilter.Topic, out var topicHash, out var topicHashMask, out var hasWildcard); MqttSubscription.CalculateTopicHash(topicFilter.Topic, out var topicHash, out var topicHashMask, out var hasWildcard);
@@ -399,10 +387,6 @@ namespace MQTTnet.Server
subscriptions.Add(subscription); subscriptions.Add(subscription);
} }
} }
finally
{
_subscriptionsLock.Release();
}
return new CreateSubscriptionResult return new CreateSubscriptionResult
{ {
@@ -412,14 +396,14 @@ namespace MQTTnet.Server
} }
static void FilterRetainedApplicationMessages( static void FilterRetainedApplicationMessages(
IList<MqttApplicationMessage> retainedApplicationMessages, IList<MqttApplicationMessage> retainedMessages,
CreateSubscriptionResult createSubscriptionResult, CreateSubscriptionResult createSubscriptionResult,
SubscribeResult subscribeResult) SubscribeResult subscribeResult)
{ {
for (var index = retainedApplicationMessages.Count - 1; index >= 0; index--) for (var index = retainedMessages.Count - 1; index >= 0; index--)
{ {
var retainedApplicationMessage = retainedApplicationMessages[index]; var retainedMessage = retainedMessages[index];
if (retainedApplicationMessage == null) if (retainedMessage == null)
{ {
continue; continue;
} }
@@ -436,16 +420,21 @@ namespace MQTTnet.Server
continue; continue;
} }
if (MqttTopicFilterComparer.Compare(retainedApplicationMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch) if (MqttTopicFilterComparer.Compare(retainedMessage.Topic, createSubscriptionResult.Subscription.Topic) != MqttTopicFilterCompareResult.IsMatch)
{ {
continue; continue;
} }
var retainedMessageMatch = new MqttRetainedMessageMatch var retainedMessageMatch = new MqttRetainedMessageMatch(retainedMessage, createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel);
if (retainedMessageMatch.SubscriptionQualityOfServiceLevel > retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel)
{ {
ApplicationMessage = retainedApplicationMessage, // UPGRADING the QoS is not allowed!
SubscriptionQualityOfServiceLevel = createSubscriptionResult.Subscription.GrantedQualityOfServiceLevel // From MQTT spec: Subscribing to a Topic Filter at QoS 2 is equivalent to saying
}; // "I would like to receive Messages matching this filter at the QoS with which they were published".
// This means a publisher is responsible for determining the maximum QoS a Message can be delivered at,
// but a subscriber is able to require that the Server downgrades the QoS to one more suitable for its usage.
retainedMessageMatch.SubscriptionQualityOfServiceLevel = retainedMessageMatch.ApplicationMessage.QualityOfServiceLevel;
}
if (subscribeResult.RetainedMessages == null) if (subscribeResult.RetainedMessages == null)
{ {
@@ -456,7 +445,7 @@ namespace MQTTnet.Server
// Clear the retained message from the list because the client should receive every message only // Clear the retained message from the list because the client should receive every message only
// one time even if multiple subscriptions affect them. // one time even if multiple subscriptions affect them.
retainedApplicationMessages[index] = null; retainedMessages[index] = null;
} }
} }
@@ -491,23 +480,20 @@ namespace MQTTnet.Server
async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken) async Task<InterceptingUnsubscriptionEventArgs> InterceptUnsubscribe(string topicFilter, MqttSubscription mqttSubscription, CancellationToken cancellationToken)
{ {
var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter); var clientUnsubscribingTopicEventArgs = new InterceptingUnsubscriptionEventArgs(cancellationToken, topicFilter, _session.Items, topicFilter)
if (mqttSubscription == null)
{ {
clientUnsubscribingTopicEventArgs.Response.ReasonCode = MqttUnsubscribeReasonCode.NoSubscriptionExisted; Response =
} {
else ReasonCode = mqttSubscription == null ? MqttUnsubscribeReasonCode.NoSubscriptionExisted : MqttUnsubscribeReasonCode.Success
{ }
clientUnsubscribingTopicEventArgs.Response.ReasonCode = MqttUnsubscribeReasonCode.Success; };
}
await _eventContainer.InterceptingUnsubscriptionEvent.InvokeAsync(clientUnsubscribingTopicEventArgs).ConfigureAwait(false); await _eventContainer.InterceptingUnsubscriptionEvent.InvokeAsync(clientUnsubscribingTopicEventArgs).ConfigureAwait(false);
return clientUnsubscribingTopicEventArgs; return clientUnsubscribingTopicEventArgs;
} }
public sealed class CreateSubscriptionResult sealed class CreateSubscriptionResult
{ {
public bool IsNewSubscription { get; set; } public bool IsNewSubscription { get; set; }
@@ -5,10 +5,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server
@@ -85,7 +83,7 @@ namespace MQTTnet.Server
} }
else else
{ {
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? PlatformAbstractionLayer.EmptyByteArray)) if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? EmptyBuffer.Array))
{ {
_messages[applicationMessage.Topic] = applicationMessage; _messages[applicationMessage.Topic] = applicationMessage;
saveIsRequired = true; saveIsRequired = true;
@@ -103,7 +101,7 @@ namespace MQTTnet.Server
if (saveIsRequired) if (saveIsRequired)
{ {
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _storageAccessLock.EnterAsync().ConfigureAwait(false))
{ {
var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave); var eventArgs = new RetainedMessageChangedEventArgs(clientId, applicationMessage, messagesForSave);
await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); await _eventContainer.RetainedMessageChangedEvent.InvokeAsync(eventArgs).ConfigureAwait(false);
@@ -132,7 +130,7 @@ namespace MQTTnet.Server
_messages.Clear(); _messages.Clear();
} }
using (await _storageAccessLock.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _storageAccessLock.EnterAsync().ConfigureAwait(false))
{ {
await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false); await _eventContainer.RetainedMessagesClearedEvent.InvokeAsync(EventArgs.Empty).ConfigureAwait(false);
} }
@@ -6,7 +6,6 @@ using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Protocol; using MQTTnet.Protocol;
@@ -14,16 +13,20 @@ namespace MQTTnet.Server
{ {
public sealed class MqttServerKeepAliveMonitor public sealed class MqttServerKeepAliveMonitor
{ {
readonly MqttNetSourceLogger _logger;
readonly MqttServerOptions _options; readonly MqttServerOptions _options;
readonly MqttClientSessionsManager _sessionsManager; readonly MqttClientSessionsManager _sessionsManager;
readonly MqttNetSourceLogger _logger;
public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger) public MqttServerKeepAliveMonitor(MqttServerOptions options, MqttClientSessionsManager sessionsManager, IMqttNetLogger logger)
{ {
_options = options ?? throw new ArgumentNullException(nameof(options)); _options = options ?? throw new ArgumentNullException(nameof(options));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
if (logger == null) throw new ArgumentNullException(nameof(logger)); if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
_logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor)); _logger = logger.WithSource(nameof(MqttServerKeepAliveMonitor));
} }
@@ -45,7 +48,7 @@ namespace MQTTnet.Server
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
TryProcessClients(); TryProcessClients();
PlatformAbstractionLayer.Sleep(_options.KeepAliveMonitorInterval); Sleep(_options.KeepAliveMonitorInterval);
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -61,13 +64,22 @@ namespace MQTTnet.Server
} }
} }
void TryProcessClients() static void Sleep(TimeSpan timeout)
{ {
var now = DateTime.UtcNow; #if !NETSTANDARD1_3 && !WINDOWS_UWP
foreach (var client in _sessionsManager.GetClients()) try
{ {
TryProcessClient(client, now); Thread.Sleep(timeout);
} }
catch (ThreadAbortException)
{
// The ThreadAbortException is not actively catched in this project.
// So we use a one which is similar and will be catched properly.
throw new OperationCanceledException();
}
#else
Task.Delay(timeout).Wait();
#endif
} }
void TryProcessClient(MqttClient connection, DateTime now) void TryProcessClient(MqttClient connection, DateTime now)
@@ -119,5 +131,14 @@ namespace MQTTnet.Server
_logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id); _logger.Error(exception, "Client {0}: Unhandled exception while checking keep alive timeouts.", connection.Id);
} }
} }
void TryProcessClients()
{
var now = DateTime.UtcNow;
foreach (var client in _sessionsManager.GetClients())
{
TryProcessClient(client, now);
}
}
} }
} }
+37 -13
View File
@@ -23,6 +23,7 @@ namespace MQTTnet.Server
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttServerOptions _serverOptions; readonly MqttServerOptions _serverOptions;
readonly MqttClientSubscriptionsManager _subscriptionsManager;
readonly ConcurrentDictionary<ushort, MqttPublishPacket> _unacknowledgedPublishPackets = new ConcurrentDictionary<ushort, MqttPublishPacket>(); readonly ConcurrentDictionary<ushort, MqttPublishPacket> _unacknowledgedPublishPackets = new ConcurrentDictionary<ushort, MqttPublishPacket>();
@@ -45,7 +46,7 @@ namespace MQTTnet.Server
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager)); _clientSessionsManager = clientSessionsManager ?? throw new ArgumentNullException(nameof(clientSessionsManager));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, eventContainer, retainedMessagesManager, clientSessionsManager); _subscriptionsManager = new MqttClientSubscriptionsManager(this, eventContainer, retainedMessagesManager, clientSessionsManager);
} }
public DateTime CreatedTimestamp { get; } = DateTime.UtcNow; public DateTime CreatedTimestamp { get; } = DateTime.UtcNow;
@@ -67,18 +68,8 @@ namespace MQTTnet.Server
public long PendingDataPacketsCount => _packetBus.PartitionItemsCount(MqttPacketBusPartition.Data); public long PendingDataPacketsCount => _packetBus.PartitionItemsCount(MqttPacketBusPartition.Data);
public MqttClientSubscriptionsManager SubscriptionsManager { get; }
public bool WillMessageSent { get; set; } public bool WillMessageSent { get; set; }
public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier)
{
// This will only return the matching PUBLISH packet but does not remove it.
// This is required for QoS 2.
_unacknowledgedPublishPackets.TryGetValue(packetIdentifier, out var publishPacket);
return publishPacket;
}
public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier) public MqttPublishPacket AcknowledgePublishPacket(ushort packetIdentifier)
{ {
_unacknowledgedPublishPackets.TryRemove(packetIdentifier, out var publishPacket); _unacknowledgedPublishPackets.TryRemove(packetIdentifier, out var publishPacket);
@@ -107,8 +98,8 @@ namespace MQTTnet.Server
public void Dispose() public void Dispose()
{ {
_packetBus?.Dispose(); _packetBus.Dispose();
SubscriptionsManager.Dispose(); _subscriptionsManager.Dispose();
} }
public void EnqueueControlPacket(MqttPacketBusItem packetBusItem) public void EnqueueControlPacket(MqttPacketBusItem packetBusItem)
@@ -150,6 +141,14 @@ namespace MQTTnet.Server
_packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health); _packetBus.EnqueueItem(packetBusItem, MqttPacketBusPartition.Health);
} }
public MqttPublishPacket PeekAcknowledgePublishPacket(ushort packetIdentifier)
{
// This will only return the matching PUBLISH packet but does not remove it.
// This is required for QoS 2.
_unacknowledgedPublishPackets.TryGetValue(packetIdentifier, out var publishPacket);
return publishPacket;
}
public void Recover() public void Recover()
{ {
// TODO: Keep the bus and only insert pending items again. // TODO: Keep the bus and only insert pending items again.
@@ -167,5 +166,30 @@ namespace MQTTnet.Server
{ {
_subscribedTopics?.Remove(topic); _subscribedTopics?.Remove(topic);
} }
public Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
return _subscriptionsManager.Subscribe(subscribePacket, cancellationToken);
}
public bool TryCheckSubscriptions(string topic, ulong topicHash, MqttQualityOfServiceLevel qualityOfServiceLevel, string senderId, out CheckSubscriptionsResult result)
{
result = null;
try
{
result = _subscriptionsManager.CheckSubscriptions(topic, topicHash, qualityOfServiceLevel, senderId);
return true;
}
catch
{
return false;
}
}
public Task<UnsubscribeResult> Unsubscribe(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
return _subscriptionsManager.Unsubscribe(unsubscribePacket, cancellationToken);
}
} }
} }
@@ -179,7 +179,7 @@ namespace MQTTnet.Server
RetainAsPublished = retainAsPublished; RetainAsPublished = retainAsPublished;
GrantedQualityOfServiceLevel = qualityOfServiceLevel; GrantedQualityOfServiceLevel = qualityOfServiceLevel;
Identifier = identifier; Identifier = identifier;
CalculateTopicHash(Topic, out var hash, out var hashMask, out var hasWildcard); CalculateTopicHash(Topic, out var hash, out var hashMask, out var hasWildcard);
TopicHash = hash; TopicHash = hash;
TopicHashMask = hashMask; TopicHashMask = hashMask;
@@ -10,8 +10,13 @@ namespace MQTTnet.Server
{ {
public sealed class SubscribeResult public sealed class SubscribeResult
{ {
public SubscribeResult(int topicsCount)
{
ReasonCodes = new List<MqttSubscribeReasonCode>(topicsCount);
}
public bool CloseConnection { get; set; } public bool CloseConnection { get; set; }
public List<MqttSubscribeReasonCode> ReasonCodes { get; set; } public List<MqttSubscribeReasonCode> ReasonCodes { get; set; }
public string ReasonString { get; set; } public string ReasonString { get; set; }

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