mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-18 02:13:00 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b85b8059 | ||
|
|
e88b280eb0 | ||
|
|
e4b07f2b8c | ||
|
|
4eb6e77eb5 | ||
|
|
db1fc7625d | ||
|
|
bfec9dc5ad | ||
|
|
56f0a2c31c | ||
|
|
5ee0bb4b65 | ||
|
|
5c74728387 | ||
|
|
97c46bea0f | ||
|
|
e6c1ab4e4c | ||
|
|
6e1ec266a7 | ||
|
|
db2c164dbf | ||
|
|
30426e5e1e | ||
|
|
3fc6982e9a | ||
|
|
5b1cc7d713 | ||
|
|
2842f27522 | ||
|
|
7ef137e26e | ||
|
|
b1a336eb62 | ||
|
|
d481e93832 | ||
|
|
8cf5537043 | ||
|
|
d2ce9d607a | ||
|
|
36736914bc | ||
|
|
7823d8342e | ||
|
|
431142471f | ||
|
|
a3227a444a |
@@ -1,7 +1 @@
|
|||||||
* [Core] MQTT Packets being sent over web socket transport are now setting the web socket frame boundaries correctly (#1499).
|
|
||||||
* [Core] Add support for attaching and detaching events from different threads.
|
|
||||||
* [Core] Fixed a deadlock in _AsyncLock_ implementation (#1520).
|
|
||||||
* [Client] Keep alive mechanism now uses the configured timeout value from the options (thanks to @Stannieman, #1495).
|
|
||||||
* [Client] The _PingAsync_ will fallback to the timeout specified in the client options when the cancellation token cannot be cancelled.
|
|
||||||
* [Server] A DISCONNECT packet is no longer sent to MQTT clients < 5.0.0 (thanks to @logicaloud, #1506).
|
|
||||||
* [Server] Improved "take over" process handling.
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
|
|
||||||
Task StartAsync(ManagedMqttClientOptions options);
|
Task StartAsync(ManagedMqttClientOptions options);
|
||||||
|
|
||||||
Task StopAsync();
|
Task StopAsync(bool cleanDisconnect = true);
|
||||||
|
|
||||||
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters);
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
CancellationTokenSource _publishingCancellationToken;
|
CancellationTokenSource _publishingCancellationToken;
|
||||||
|
|
||||||
ManagedMqttClientStorageManager _storageManager;
|
ManagedMqttClientStorageManager _storageManager;
|
||||||
|
bool _isCleanDisconnect;
|
||||||
|
|
||||||
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
public ManagedMqttClient(IMqttClient mqttClient, IMqttNetLogger logger)
|
||||||
{
|
{
|
||||||
@@ -160,14 +161,14 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
throw new InvalidOperationException("call StartAsync before publishing messages");
|
throw new InvalidOperationException("call StartAsync before publishing messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage.Topic);
|
MqttTopicValidator.ThrowIfInvalid(applicationMessage.ApplicationMessage);
|
||||||
|
|
||||||
ManagedMqttApplicationMessage removedMessage = null;
|
ManagedMqttApplicationMessage removedMessage = null;
|
||||||
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
ApplicationMessageSkippedEventArgs applicationMessageSkippedEventArgs = null;
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -255,10 +256,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
_logger.Info("Started");
|
_logger.Info("Started");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StopAsync()
|
public async Task StopAsync(bool cleanDisconnect = true)
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
_isCleanDisconnect = cleanDisconnect;
|
||||||
|
|
||||||
StopPublishing();
|
StopPublishing();
|
||||||
StopMaintainingConnection();
|
StopMaintainingConnection();
|
||||||
|
|
||||||
@@ -296,7 +299,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 +322,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)
|
||||||
@@ -382,9 +385,12 @@ namespace MQTTnet.Extensions.ManagedClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
if (_isCleanDisconnect)
|
||||||
{
|
{
|
||||||
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
using (var disconnectTimeout = new CancellationTokenSource(Options.ClientOptions.Timeout))
|
||||||
|
{
|
||||||
|
await InternalClient.DisconnectAsync(new MqttClientDisconnectOptions(), disconnectTimeout.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -693,7 +699,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 +730,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()
|
||||||
|
|||||||
@@ -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))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 =>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
|
|
||||||
await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false);
|
await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[ExpectedException(typeof(MqttCommunicationException))]
|
[ExpectedException(typeof(MqttCommunicationException))]
|
||||||
public async Task Connect_To_Wrong_Host()
|
public async Task Connect_To_Wrong_Host()
|
||||||
@@ -80,6 +80,8 @@ namespace MQTTnet.Tests.Client
|
|||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
|
testEnvironment.IgnoreServerLogErrors = true;
|
||||||
|
|
||||||
testEnvironment.ServerPort = 8364;
|
testEnvironment.ServerPort = 8364;
|
||||||
var server = await testEnvironment.StartServer();
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
@@ -116,6 +118,8 @@ namespace MQTTnet.Tests.Client
|
|||||||
{
|
{
|
||||||
using (var testEnvironment = CreateTestEnvironment())
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
{
|
{
|
||||||
|
testEnvironment.IgnoreServerLogErrors = true;
|
||||||
|
|
||||||
var server = await testEnvironment.StartServer();
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
using (var lowLevelClient = testEnvironment.CreateLowLevelClient())
|
using (var lowLevelClient = testEnvironment.CreateLowLevelClient())
|
||||||
@@ -123,7 +127,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
Assert.IsFalse(lowLevelClient.IsConnected);
|
Assert.IsFalse(lowLevelClient.IsConnected);
|
||||||
|
|
||||||
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build();
|
var clientOptions = new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).WithTimeout(TimeSpan.FromSeconds(1)).Build();
|
||||||
|
|
||||||
await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None);
|
await lowLevelClient.ConnectAsync(clientOptions, CancellationToken.None);
|
||||||
|
|
||||||
Assert.IsTrue(lowLevelClient.IsConnected);
|
Assert.IsTrue(lowLevelClient.IsConnected);
|
||||||
@@ -148,7 +152,7 @@ namespace MQTTnet.Tests.Client
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.IsFalse(lowLevelClient.IsConnected);
|
Assert.IsFalse(lowLevelClient.IsConnected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,76 @@ 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.Formatter;
|
||||||
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 Receive_While_Not_Cleanly_Disconnected()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||||
|
|
||||||
|
var senderClient = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
// Prepare managed client.
|
||||||
|
var managedClient = testEnvironment.Factory.CreateManagedMqttClient();
|
||||||
|
await managedClient.SubscribeAsync("#");
|
||||||
|
var receivedMessages = testEnvironment.CreateApplicationMessageHandler(managedClient);
|
||||||
|
|
||||||
|
var managedClientOptions = new ManagedMqttClientOptions
|
||||||
|
{
|
||||||
|
ClientOptions = testEnvironment.Factory.CreateClientOptionsBuilder()
|
||||||
|
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
|
||||||
|
.WithClientId(nameof(Receive_While_Not_Cleanly_Disconnected) + "_managed")
|
||||||
|
.WithCleanSession(false)
|
||||||
|
.Build()
|
||||||
|
};
|
||||||
|
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send test data.
|
||||||
|
await senderClient.PublishStringAsync("topic1");
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(1);
|
||||||
|
|
||||||
|
// Stop the managed client but keep session at server (not clean disconnect required).
|
||||||
|
await managedClient.StopAsync(false);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Send new messages in the meantime.
|
||||||
|
await senderClient.PublishStringAsync("topic2", qualityOfServiceLevel: MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Start the managed client, it should receive the new message.
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(2);
|
||||||
|
|
||||||
|
// Stop and start again, no new message should be received.
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
await managedClient.StopAsync(false);
|
||||||
|
await LongTestDelay();
|
||||||
|
await managedClient.StartAsync(managedClientOptions);
|
||||||
|
await LongTestDelay();
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedMessages.AssertReceivedCountEquals(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Connect_To_Invalid_Server()
|
public async Task Connect_To_Invalid_Server()
|
||||||
{
|
{
|
||||||
@@ -35,11 +93,10 @@ namespace MQTTnet.Tests.Client
|
|||||||
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
|
var managedClient = testEnvironment.Factory.CreateManagedMqttClient(mqttClient);
|
||||||
|
|
||||||
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
ConnectingFailedEventArgs connectingFailedEventArgs = null;
|
||||||
|
|
||||||
managedClient.ConnectingFailedAsync += args =>
|
managedClient.ConnectingFailedAsync += args =>
|
||||||
{
|
{
|
||||||
connectingFailedEventArgs = args;
|
connectingFailedEventArgs = args;
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
await managedClient.StartAsync(
|
await managedClient.StartAsync(
|
||||||
@@ -117,7 +174,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 +338,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 +347,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 +377,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 +488,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 +517,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 +546,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,128 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Internal
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class AsyncSignal_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(TaskCanceledException))]
|
||||||
|
public async Task Cancel_If_No_Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)))
|
||||||
|
{
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
Assert.Fail("There is no signal. So we must fail here!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(ObjectDisposedException))]
|
||||||
|
public async Task Dispose_Properly()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// The timeout will not be reached but another task will kill the async signal.
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(999)))
|
||||||
|
{
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(2000, CancellationToken.None);
|
||||||
|
asyncSignal.Dispose();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
Assert.Fail("There is no signal. So we must fail here!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Reset_Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// WaitAsync should fail because no signal is available.
|
||||||
|
for (var i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)))
|
||||||
|
{
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Fail("This must fail because the signal is not yet set.");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncSignal.Set();
|
||||||
|
|
||||||
|
// WaitAsync should return directly because the signal is available.
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Signal()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// The timeout will not be reached but another task will kill the async signal.
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000, CancellationToken.None);
|
||||||
|
asyncSignal.Set();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await asyncSignal.WaitAsync(timeout.Token);
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
Assert.IsTrue(stopwatch.ElapsedMilliseconds > 900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(InvalidOperationException))]
|
||||||
|
public async Task Fail_For_Two_Waiters()
|
||||||
|
{
|
||||||
|
var asyncSignal = new AsyncSignal();
|
||||||
|
|
||||||
|
// This thread will wait properly because it is the first waiter.
|
||||||
|
_ = Task.Run(
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
},
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
|
||||||
|
// Now the current thread must fail because there is already a waiter.
|
||||||
|
await asyncSignal.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Internal;
|
||||||
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Internal
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class MqttPacketBusItem_Tests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Fire_Completed_Event()
|
||||||
|
{
|
||||||
|
var eventFired = false;
|
||||||
|
|
||||||
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
|
item.Completed += (_, __) =>
|
||||||
|
{
|
||||||
|
eventFired = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
item.Complete();
|
||||||
|
|
||||||
|
Assert.IsTrue(eventFired);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(TaskCanceledException))]
|
||||||
|
public async Task Wait_Packet_Bus_Item_After_Already_Canceled()
|
||||||
|
{
|
||||||
|
var item = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
|
|
||||||
|
// Finish the item before the actual
|
||||||
|
item.Cancel();
|
||||||
|
|
||||||
|
await item.WaitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,69 +13,26 @@ namespace MQTTnet.Tests.Internal
|
|||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class MqttPacketBus_Tests
|
public sealed class MqttPacketBus_Tests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
[ExpectedException(typeof(OperationCanceledException))]
|
|
||||||
public async Task Wait_With_Empty_Bus()
|
|
||||||
{
|
|
||||||
var bus = new MqttPacketBus();
|
|
||||||
|
|
||||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
|
||||||
{
|
|
||||||
await bus.DequeueItemAsync(timeout.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public async Task Fill_From_Different_Task()
|
|
||||||
{
|
|
||||||
const int MessageCount = 500;
|
|
||||||
|
|
||||||
var delayRandom = new Random();
|
|
||||||
|
|
||||||
var bus = new MqttPacketBus();
|
|
||||||
|
|
||||||
_ = Task.Run(
|
|
||||||
() =>
|
|
||||||
{
|
|
||||||
for (int i = 0; i < MessageCount; i++)
|
|
||||||
{
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health);
|
|
||||||
|
|
||||||
Thread.Sleep(delayRandom.Next(0, 10));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var i = 0; i < MessageCount; i++)
|
|
||||||
{
|
|
||||||
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
|
||||||
{
|
|
||||||
await bus.DequeueItemAsync(timeout.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.AreEqual(0, bus.TotalItemsCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Alternate_Priorities()
|
public void Alternate_Priorities()
|
||||||
{
|
{
|
||||||
var bus = new MqttPacketBus();
|
var bus = new MqttPacketBus();
|
||||||
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttSubAckPacket()), MqttPacketBusPartition.Control);
|
||||||
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPingRespPacket()), MqttPacketBusPartition.Health);
|
||||||
|
|
||||||
Assert.AreEqual(9, bus.TotalItemsCount);
|
Assert.AreEqual(9, bus.TotalItemsCount);
|
||||||
|
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
||||||
@@ -85,33 +42,10 @@ namespace MQTTnet.Tests.Internal
|
|||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPublishPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttSubAckPacket));
|
||||||
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
Assert.IsInstanceOfType(bus.DequeueItemAsync(CancellationToken.None).Result.Packet, typeof(MqttPingRespPacket));
|
||||||
|
|
||||||
Assert.AreEqual(0, bus.TotalItemsCount);
|
Assert.AreEqual(0, bus.TotalItemsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Export_Packets_Without_Dequeue()
|
|
||||||
{
|
|
||||||
var bus = new MqttPacketBus();
|
|
||||||
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
|
||||||
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
|
||||||
|
|
||||||
Assert.AreEqual(3, bus.TotalItemsCount);
|
|
||||||
|
|
||||||
var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control);
|
|
||||||
Assert.AreEqual(0, exportedPackets.Count);
|
|
||||||
|
|
||||||
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health);
|
|
||||||
Assert.AreEqual(0, exportedPackets.Count);
|
|
||||||
|
|
||||||
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data);
|
|
||||||
Assert.AreEqual(3, exportedPackets.Count);
|
|
||||||
|
|
||||||
Assert.AreEqual(3, bus.TotalItemsCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Await_Single_Packet()
|
public void Await_Single_Packet()
|
||||||
{
|
{
|
||||||
@@ -121,31 +55,97 @@ namespace MQTTnet.Tests.Internal
|
|||||||
|
|
||||||
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item1 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item2 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
|
|
||||||
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
|
var item3 = new MqttPacketBusItem(new MqttPublishPacket());
|
||||||
item3.Delivered += (_, __) =>
|
item3.Completed += (_, __) =>
|
||||||
{
|
{
|
||||||
delivered = true;
|
delivered = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item1, MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item2, MqttPacketBusPartition.Data);
|
||||||
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
|
bus.EnqueueItem(item3, MqttPacketBusPartition.Data);
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
Assert.IsFalse(delivered);
|
Assert.IsFalse(delivered);
|
||||||
|
|
||||||
bus.DequeueItemAsync(CancellationToken.None).Result.MarkAsDelivered();
|
bus.DequeueItemAsync(CancellationToken.None).Result.Complete();
|
||||||
|
|
||||||
// The third packet has the event attached.
|
// The third packet has the event attached.
|
||||||
Assert.IsTrue(delivered);
|
Assert.IsTrue(delivered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Export_Packets_Without_Dequeue()
|
||||||
|
{
|
||||||
|
var bus = new MqttPacketBus();
|
||||||
|
|
||||||
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
|
bus.EnqueueItem(new MqttPacketBusItem(new MqttPublishPacket()), MqttPacketBusPartition.Data);
|
||||||
|
|
||||||
|
Assert.AreEqual(3, bus.TotalItemsCount);
|
||||||
|
|
||||||
|
var exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Control);
|
||||||
|
Assert.AreEqual(0, exportedPackets.Count);
|
||||||
|
|
||||||
|
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Health);
|
||||||
|
Assert.AreEqual(0, exportedPackets.Count);
|
||||||
|
|
||||||
|
exportedPackets = bus.ExportPackets(MqttPacketBusPartition.Data);
|
||||||
|
Assert.AreEqual(3, exportedPackets.Count);
|
||||||
|
|
||||||
|
Assert.AreEqual(3, bus.TotalItemsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Fill_From_Different_Task()
|
||||||
|
{
|
||||||
|
const int MessageCount = 500;
|
||||||
|
|
||||||
|
var delayRandom = new Random();
|
||||||
|
|
||||||
|
var bus = new MqttPacketBus();
|
||||||
|
|
||||||
|
_ = Task.Run(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
for (var i = 0; i < MessageCount; i++)
|
||||||
|
{
|
||||||
|
bus.EnqueueItem(new MqttPacketBusItem(MqttPingReqPacket.Instance), MqttPacketBusPartition.Health);
|
||||||
|
|
||||||
|
Thread.Sleep(delayRandom.Next(0, 10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var i = 0; i < MessageCount; i++)
|
||||||
|
{
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||||
|
{
|
||||||
|
await bus.DequeueItemAsync(timeout.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(0, bus.TotalItemsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(TaskCanceledException))]
|
||||||
|
public async Task Wait_With_Empty_Bus()
|
||||||
|
{
|
||||||
|
var bus = new MqttPacketBus();
|
||||||
|
|
||||||
|
using (var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(1)))
|
||||||
|
{
|
||||||
|
await bus.DequeueItemAsync(timeout.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -155,7 +154,7 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce);
|
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
await client.DisconnectAsync();
|
await client.DisconnectAsync();
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode);
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ namespace MQTTnet.Tests.MQTTv5
|
|||||||
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce);
|
var result = await client.PublishStringAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce);
|
||||||
await client.DisconnectAsync();
|
await client.DisconnectAsync();
|
||||||
|
|
||||||
Assert.AreEqual(MqttClientPublishReasonCode.Success, result.ReasonCode);
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, result.ReasonCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Internal;
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Mockups
|
namespace MQTTnet.Tests.Mockups
|
||||||
{
|
{
|
||||||
@@ -17,7 +19,12 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
|
|
||||||
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
|
public TestApplicationMessageReceivedHandler(IMqttClient mqttClient)
|
||||||
{
|
{
|
||||||
mqttClient.ApplicationMessageReceivedAsync += MqttClientOnApplicationMessageReceivedAsync;
|
if (mqttClient == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(mqttClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
mqttClient.ApplicationMessageReceivedAsync += OnApplicationMessageReceivedAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
|
public List<MqttApplicationMessageReceivedEventArgs> ReceivedEventArgs
|
||||||
@@ -39,14 +46,32 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task MqttClientOnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
public string GeneratePayloadSequence()
|
||||||
|
{
|
||||||
|
var sequence = new StringBuilder();
|
||||||
|
|
||||||
|
lock (_receivedEventArgs)
|
||||||
|
{
|
||||||
|
foreach (var receivedEventArg in _receivedEventArgs)
|
||||||
|
{
|
||||||
|
var payload = receivedEventArg.ApplicationMessage.ConvertPayloadToString();
|
||||||
|
|
||||||
|
// An empty payload is not part of the sequence!
|
||||||
|
sequence.Append(payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Task OnApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
lock (_receivedEventArgs)
|
lock (_receivedEventArgs)
|
||||||
{
|
{
|
||||||
_receivedEventArgs.Add(eventArgs);
|
_receivedEventArgs.Add(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return CompletedTask.Instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,9 +11,10 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using MQTTnet.Client;
|
using MQTTnet.Client;
|
||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
|
using MQTTnet.Extensions.ManagedClient;
|
||||||
using MQTTnet.Extensions.Rpc;
|
using MQTTnet.Extensions.Rpc;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.LowLevelClient;
|
using MQTTnet.LowLevelClient;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
using MQTTnet.Server;
|
using MQTTnet.Server;
|
||||||
@@ -109,7 +110,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
configureOptions.Invoke(optionsBuilder);
|
configureOptions.Invoke(optionsBuilder);
|
||||||
|
|
||||||
var options = optionsBuilder.Build();
|
var options = optionsBuilder.Build();
|
||||||
|
|
||||||
var client = CreateClient();
|
var client = CreateClient();
|
||||||
|
|
||||||
if (timeout == TimeSpan.Zero)
|
if (timeout == TimeSpan.Zero)
|
||||||
@@ -137,7 +138,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
options = options.WithTcpServer("127.0.0.1", ServerPort);
|
options = options.WithTcpServer("127.0.0.1", ServerPort);
|
||||||
|
|
||||||
var client = CreateClient();
|
var client = CreateClient();
|
||||||
|
|
||||||
if (timeout == TimeSpan.Zero)
|
if (timeout == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
await client.ConnectAsync(options.Build()).ConfigureAwait(false);
|
await client.ConnectAsync(options.Build()).ConfigureAwait(false);
|
||||||
@@ -161,7 +162,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
|
|
||||||
var client = CreateClient();
|
var client = CreateClient();
|
||||||
|
|
||||||
if (timeout == TimeSpan.Zero)
|
if (timeout == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
await client.ConnectAsync(options).ConfigureAwait(false);
|
await client.ConnectAsync(options).ConfigureAwait(false);
|
||||||
@@ -199,6 +200,16 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
return new TestApplicationMessageReceivedHandler(mqttClient);
|
return new TestApplicationMessageReceivedHandler(mqttClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TestApplicationMessageReceivedHandler CreateApplicationMessageHandler(IManagedMqttClient managedClient)
|
||||||
|
{
|
||||||
|
if (managedClient == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(managedClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TestApplicationMessageReceivedHandler(managedClient.InternalClient);
|
||||||
|
}
|
||||||
|
|
||||||
public IMqttClient CreateClient()
|
public IMqttClient CreateClient()
|
||||||
{
|
{
|
||||||
lock (_clients)
|
lock (_clients)
|
||||||
@@ -220,7 +231,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
@@ -261,7 +272,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlatformAbstractionLayer.CompletedTask;
|
return CompletedTask.Instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
return Server;
|
return Server;
|
||||||
@@ -325,7 +336,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
var options = optionsBuilder.Build();
|
var options = optionsBuilder.Build();
|
||||||
var server = CreateServer(options);
|
var server = CreateServer(options);
|
||||||
await server.StartAsync();
|
await server.StartAsync();
|
||||||
|
|
||||||
// The OS has chosen the port to we have to properly expose it to the tests.
|
// The OS has chosen the port to we have to properly expose it to the tests.
|
||||||
ServerPort = options.DefaultEndpointOptions.Port;
|
ServerPort = options.DefaultEndpointOptions.Port;
|
||||||
return server;
|
return server;
|
||||||
@@ -344,7 +355,7 @@ namespace MQTTnet.Tests.Mockups
|
|||||||
var options = optionsBuilder.Build();
|
var options = optionsBuilder.Build();
|
||||||
var server = CreateServer(options);
|
var server = CreateServer(options);
|
||||||
await server.StartAsync();
|
await server.StartAsync();
|
||||||
|
|
||||||
// The OS has chosen the port to we have to properly expose it to the tests.
|
// The OS has chosen the port to we have to properly expose it to the tests.
|
||||||
ServerPort = options.DefaultEndpointOptions.Port;
|
ServerPort = options.DefaultEndpointOptions.Port;
|
||||||
return server;
|
return server;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(120));
|
||||||
|
|
||||||
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,58 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Formatter;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Publish_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_Success_When_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("A", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.Success, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Return_NoMatchingSubscribers_When_Not_Subscribed()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
|
||||||
|
{
|
||||||
|
await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
var sender = await testEnvironment.ConnectClient();
|
||||||
|
var receiver = await testEnvironment.ConnectClient();
|
||||||
|
|
||||||
|
await receiver.SubscribeAsync("A");
|
||||||
|
|
||||||
|
// AtLeastOnce is required to get an ACK packet from the server.
|
||||||
|
var publishResult = await sender.PublishStringAsync("B", "Payload", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
Assert.AreEqual(MqttClientPublishReasonCode.NoMatchingSubscribers, publishResult.ReasonCode);
|
||||||
|
|
||||||
|
Assert.AreEqual(true, publishResult.IsSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using MQTTnet.Client;
|
||||||
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
|
namespace MQTTnet.Tests.Server
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public sealed class Publishing_Tests : BaseTestClass
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
[ExpectedException(typeof(MqttClientDisconnectedException))]
|
||||||
|
public async Task Disconnect_While_Publishing()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer();
|
||||||
|
|
||||||
|
// The client will be disconnect directly after subscribing!
|
||||||
|
server.InterceptingPublishAsync += ev => server.DisconnectClientAsync(ev.ClientId, MqttDisconnectReasonCode.NormalDisconnection);
|
||||||
|
|
||||||
|
var client = await testEnvironment.ConnectClient();
|
||||||
|
await client.PublishStringAsync("test", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -15,6 +15,57 @@ namespace MQTTnet.Tests.Server
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public sealed class QoS_Tests : BaseTestClass
|
public sealed class QoS_Tests : BaseTestClass
|
||||||
{
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Preserve_Message_Order_For_Queued_Messages()
|
||||||
|
{
|
||||||
|
using (var testEnvironment = CreateTestEnvironment())
|
||||||
|
{
|
||||||
|
var server = await testEnvironment.StartServer(o => o.WithPersistentSessions());
|
||||||
|
|
||||||
|
// Create a session which will contain the messages.
|
||||||
|
var dummyClient = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
|
||||||
|
await dummyClient.SubscribeAsync("#", MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
dummyClient.Dispose();
|
||||||
|
|
||||||
|
// Now inject messages which are appended to the queue of the client.
|
||||||
|
await server.InjectApplicationMessage("T", "0", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await server.InjectApplicationMessage("T", "2", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
await server.InjectApplicationMessage("T", "1", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await server.InjectApplicationMessage("T", "4", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
await server.InjectApplicationMessage("T", "3", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await server.InjectApplicationMessage("T", "6", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
await server.InjectApplicationMessage("T", "5", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await server.InjectApplicationMessage("T", "8", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
await server.InjectApplicationMessage("T", "7", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await server.InjectApplicationMessage("T", "9", qualityOfServiceLevel: MqttQualityOfServiceLevel.AtLeastOnce);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
// Create a new client for the existing message.
|
||||||
|
var client = await testEnvironment.ConnectClient(o => o.WithClientId("A").WithCleanSession(false));
|
||||||
|
var messages = testEnvironment.CreateApplicationMessageHandler(client);
|
||||||
|
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
var payloadSequence = messages.GeneratePayloadSequence();
|
||||||
|
Assert.AreEqual("0214365879", payloadSequence);
|
||||||
|
|
||||||
|
// Disconnect and reconnect to make sure that the server will not send the messages twice.
|
||||||
|
await client.DisconnectAsync();
|
||||||
|
await LongTestDelay();
|
||||||
|
await client.ReconnectAsync();
|
||||||
|
await LongTestDelay();
|
||||||
|
|
||||||
|
payloadSequence = messages.GeneratePayloadSequence();
|
||||||
|
Assert.AreEqual("0214365879", payloadSequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Fire_Event_On_Client_Acknowledges_QoS_0()
|
public async Task Fire_Event_On_Client_Acknowledges_QoS_0()
|
||||||
{
|
{
|
||||||
@@ -26,7 +77,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 +104,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 +139,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 +150,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
|
||||||
|
|||||||
@@ -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,23 +150,24 @@ namespace MQTTnet.Tests.Server
|
|||||||
// Try to connect 50 clients at the same time.
|
// Try to connect 50 clients at the same time.
|
||||||
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
var clients = await Task.WhenAll(Enumerable.Range(0, 50).Select(i => ConnectAndSubscribe(testEnvironment, options, OnReceive)));
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||||
|
|
||||||
|
var connectedClients = clients.Where(c => c?.TryPingAsync().GetAwaiter().GetResult() == true).ToList();
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
await Task.Delay(3000);
|
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MQTTnet.Tests.Server
|
namespace MQTTnet.Tests
|
||||||
{
|
{
|
||||||
public class TopicGenerator
|
public class TopicGenerator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ using MQTTnet.Channel;
|
|||||||
using MQTTnet.Diagnostics;
|
using MQTTnet.Diagnostics;
|
||||||
using MQTTnet.Exceptions;
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Formatter;
|
using MQTTnet.Formatter;
|
||||||
using MQTTnet.Implementations;
|
|
||||||
using MQTTnet.Internal;
|
using MQTTnet.Internal;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
|
|
||||||
@@ -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 canceled.", 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,11 +295,29 @@ namespace MQTTnet.Adapter
|
|||||||
|
|
||||||
while (totalBytesRead < buffer.Length)
|
while (totalBytesRead < buffer.Length)
|
||||||
{
|
{
|
||||||
var bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
// Check two times for cancellation because the call to _ReadAsync_ might block for some time.
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return ReadFixedHeaderResult.Cancelled;
|
return ReadFixedHeaderResult.Canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytesRead = await _channel.ReadAsync(buffer, totalBytesRead, buffer.Length - totalBytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.Canceled;
|
||||||
|
}
|
||||||
|
catch (SocketException)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.ConnectionClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return ReadFixedHeaderResult.Canceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytesRead == 0)
|
if (bytesRead == 0)
|
||||||
@@ -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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-3
@@ -3,14 +3,15 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using MQTTnet.Exceptions;
|
||||||
using MQTTnet.Packets;
|
using MQTTnet.Packets;
|
||||||
using MQTTnet.Protocol;
|
using MQTTnet.Protocol;
|
||||||
|
|
||||||
namespace MQTTnet.Exceptions
|
namespace MQTTnet.Client
|
||||||
{
|
{
|
||||||
public class MqttUnexpectedDisconnectReceivedException : MqttCommunicationException
|
public sealed class MqttClientUnexpectedDisconnectReceivedException : MqttCommunicationException
|
||||||
{
|
{
|
||||||
public MqttUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
|
public MqttClientUnexpectedDisconnectReceivedException(MqttDisconnectPacket disconnectPacket)
|
||||||
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
|
: base($"Unexpected DISCONNECT (Reason code={disconnectPacket.ReasonCode}) received.")
|
||||||
{
|
{
|
||||||
ReasonCode = disconnectPacket.ReasonCode;
|
ReasonCode = disconnectPacket.ReasonCode;
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,26 +14,6 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public static class MqttClientExtensions
|
public static class MqttClientExtensions
|
||||||
{
|
{
|
||||||
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
if (client == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await client.PingAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignore errors.
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
public static Task DisconnectAsync(this IMqttClient client, MqttClientDisconnectReason reason = MqttClientDisconnectReason.NormalDisconnection, string reasonString = null)
|
||||||
{
|
{
|
||||||
if (client == null)
|
if (client == null)
|
||||||
@@ -89,6 +69,17 @@ namespace MQTTnet.Client
|
|||||||
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
return mqttClient.PublishBinaryAsync(topic, payloadBuffer, qualityOfServiceLevel, retain, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client.Options == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"The MQTT client was not connected before. A reconnect is only permitted when the client was already connected or at least tried to.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.ConnectAsync(client.Options, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
|
public static Task SendExtendedAuthenticationExchangeDataAsync(this IMqttClient client, MqttExtendedAuthenticationExchangeData data)
|
||||||
{
|
{
|
||||||
if (client == null)
|
if (client == null)
|
||||||
@@ -137,6 +128,26 @@ namespace MQTTnet.Client
|
|||||||
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
|
return mqttClient.SubscribeAsync(subscribeOptions, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> TryPingAsync(this IMqttClient client, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await client.PingAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static Task<MqttClientUnsubscribeResult> UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default)
|
public static Task<MqttClientUnsubscribeResult> UnsubscribeAsync(this IMqttClient mqttClient, string topic, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (mqttClient == null)
|
if (mqttClient == null)
|
||||||
|
|||||||
@@ -10,12 +10,20 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientCertificateValidationEventArgs : EventArgs
|
public sealed class MqttClientCertificateValidationEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public X509Certificate Certificate { get; set; }
|
public MqttClientCertificateValidationEventArgs(X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors, IMqttClientChannelOptions clientOptions)
|
||||||
|
{
|
||||||
|
Certificate = certificate ?? throw new ArgumentNullException(nameof(certificate));
|
||||||
|
Chain = chain ?? throw new ArgumentNullException(nameof(chain));
|
||||||
|
SslPolicyErrors = sslPolicyErrors;
|
||||||
|
ClientOptions = clientOptions ?? throw new ArgumentNullException(nameof(clientOptions));
|
||||||
|
}
|
||||||
|
|
||||||
public X509Chain Chain { get; set; }
|
public X509Certificate Certificate { get; }
|
||||||
|
|
||||||
public SslPolicyErrors SslPolicyErrors { get; set; }
|
public X509Chain Chain { get; }
|
||||||
|
|
||||||
public IMqttClientChannelOptions ClientOptions { get; set; }
|
public IMqttClientChannelOptions ClientOptions { get; }
|
||||||
|
|
||||||
|
public SslPolicyErrors SslPolicyErrors { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,11 +12,18 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs)
|
public static bool Handle(MqttClientCertificateValidationEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (eventArgs.SslPolicyErrors == SslPolicyErrors.None)
|
var sslPolicyErrors = eventArgs.SslPolicyErrors;
|
||||||
|
|
||||||
|
if (eventArgs.ClientOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
|
||||||
|
{
|
||||||
|
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sslPolicyErrors == SslPolicyErrors.None)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eventArgs.Chain.ChainStatus.Any(c =>
|
if (eventArgs.Chain.ChainStatus.Any(c =>
|
||||||
c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
|
c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientOptionsBuilderTlsParameters
|
public sealed class MqttClientOptionsBuilderTlsParameters
|
||||||
{
|
{
|
||||||
public bool UseTls { get; set; }
|
public bool UseTls { get; set; }
|
||||||
|
|
||||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } = MqttClientDefaultCertificateValidationHandler.Handle;
|
||||||
|
|
||||||
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
#if NET48 || NETCOREAPP3_1 || NET5 || NET6
|
||||||
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
public SslProtocols SslProtocol { get; set; } = SslProtocols.Tls12 | SslProtocols.Tls13;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace MQTTnet.Client
|
|||||||
{
|
{
|
||||||
public sealed class MqttClientTlsOptions
|
public sealed class MqttClientTlsOptions
|
||||||
{
|
{
|
||||||
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; }
|
public Func<MqttClientCertificateValidationEventArgs, bool> CertificateValidationHandler { get; set; } = MqttClientDefaultCertificateValidationHandler.Handle;
|
||||||
|
|
||||||
public bool UseTls { get; set; }
|
public bool UseTls { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -10,29 +10,38 @@ namespace MQTTnet.Client
|
|||||||
public sealed class MqttClientPublishResult
|
public sealed class MqttClientPublishResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the packet identifier which was used for this publish.
|
/// Returns if the overall status of the publish is a success. This can be the reason code _Success_ or
|
||||||
|
/// _NoMatchingSubscribers_. _NoMatchingSubscribers_ usually indicates only that no other client is interested in the topic but overall transmit
|
||||||
|
/// to the server etc. was a success.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccess => ReasonCode == MqttClientPublishReasonCode.Success || ReasonCode == MqttClientPublishReasonCode.NoMatchingSubscribers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the packet identifier which was used for this publish.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ushort? PacketIdentifier { get; set; }
|
public ushort? PacketIdentifier { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the reason code.
|
/// Gets or sets the reason code.
|
||||||
/// Hint: MQTT 5 feature only.
|
/// Hint: MQTT 5 feature only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
|
public MqttClientPublishReasonCode ReasonCode { get; set; } = MqttClientPublishReasonCode.Success;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the reason string.
|
/// Gets or sets the reason string.
|
||||||
/// Hint: MQTT 5 feature only.
|
/// Hint: MQTT 5 feature only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ReasonString { get; set; }
|
public string ReasonString { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the user properties.
|
/// Gets or sets the user properties.
|
||||||
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT packet.
|
/// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT
|
||||||
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
/// packet.
|
||||||
/// The feature is very similar to the HTTP header concept.
|
/// As long as you don’t exceed the maximum message size, you can use an unlimited number of user properties to add
|
||||||
/// Hint: MQTT 5 feature only.
|
/// metadata to MQTT messages and pass information between publisher, broker, and subscriber.
|
||||||
|
/// The feature is very similar to the HTTP header concept.
|
||||||
|
/// Hint: MQTT 5 feature only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
public IReadOnlyCollection<MqttUserProperty> UserProperties { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
public sealed class MqttPubAckPacketFactory
|
public sealed class MqttPubAckPacketFactory
|
||||||
{
|
{
|
||||||
public MqttPubAckPacket Create(MqttPublishPacket publishPacket, InterceptingPublishEventArgs interceptingPublishEventArgs)
|
public MqttPubAckPacket Create(
|
||||||
|
MqttPublishPacket publishPacket,
|
||||||
|
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
||||||
|
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
{
|
{
|
||||||
@@ -21,10 +24,19 @@ namespace MQTTnet.Formatter
|
|||||||
|
|
||||||
var pubAckPacket = new MqttPubAckPacket
|
var pubAckPacket = new MqttPubAckPacket
|
||||||
{
|
{
|
||||||
PacketIdentifier = publishPacket.PacketIdentifier,
|
PacketIdentifier = publishPacket.PacketIdentifier
|
||||||
ReasonCode = MqttPubAckReasonCode.Success
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
||||||
|
{
|
||||||
|
// NoMatchingSubscribers is ONLY sent by the server!
|
||||||
|
pubAckPacket.ReasonCode = MqttPubAckReasonCode.NoMatchingSubscribers;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pubAckPacket.ReasonCode = MqttPubAckReasonCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
if (interceptingPublishEventArgs != null)
|
if (interceptingPublishEventArgs != null)
|
||||||
{
|
{
|
||||||
pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
pubAckPacket.ReasonCode = (MqttPubAckReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||||
@@ -42,24 +54,12 @@ namespace MQTTnet.Formatter
|
|||||||
throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs));
|
throw new ArgumentNullException(nameof(applicationMessageReceivedEventArgs));
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubAckPacket = Create(applicationMessageReceivedEventArgs.PublishPacket, applicationMessageReceivedEventArgs.ReasonCode);
|
|
||||||
pubAckPacket.UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties;
|
|
||||||
pubAckPacket.ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString;
|
|
||||||
|
|
||||||
return pubAckPacket;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MqttPubAckPacket Create(MqttPublishPacket publishPacket, MqttApplicationMessageReceivedReasonCode applicationMessageReceivedReasonCode)
|
|
||||||
{
|
|
||||||
if (publishPacket == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(publishPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
var pubAckPacket = new MqttPubAckPacket
|
var pubAckPacket = new MqttPubAckPacket
|
||||||
{
|
{
|
||||||
PacketIdentifier = publishPacket.PacketIdentifier,
|
PacketIdentifier = applicationMessageReceivedEventArgs.PublishPacket.PacketIdentifier,
|
||||||
ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedReasonCode
|
ReasonCode = (MqttPubAckReasonCode)(int)applicationMessageReceivedEventArgs.ReasonCode,
|
||||||
|
UserProperties = applicationMessageReceivedEventArgs.ResponseUserProperties,
|
||||||
|
ReasonString = applicationMessageReceivedEventArgs.ResponseReasonString
|
||||||
};
|
};
|
||||||
|
|
||||||
return pubAckPacket;
|
return pubAckPacket;
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ namespace MQTTnet.Formatter
|
|||||||
return pubRecPacket;
|
return pubRecPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MqttPacket Create(MqttPublishPacket publishPacket, InterceptingPublishEventArgs interceptingPublishEventArgs)
|
public MqttPacket Create(
|
||||||
|
MqttPublishPacket publishPacket,
|
||||||
|
InterceptingPublishEventArgs interceptingPublishEventArgs,
|
||||||
|
DispatchApplicationMessageResult dispatchApplicationMessageResult)
|
||||||
{
|
{
|
||||||
if (publishPacket == null)
|
if (publishPacket == null)
|
||||||
{
|
{
|
||||||
@@ -38,6 +41,11 @@ namespace MQTTnet.Formatter
|
|||||||
ReasonCode = MqttPubRecReasonCode.Success
|
ReasonCode = MqttPubRecReasonCode.Success
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (dispatchApplicationMessageResult.MatchingSubscribersCount == 0)
|
||||||
|
{
|
||||||
|
pubRecPacket.ReasonCode = MqttPubRecReasonCode.NoMatchingSubscribers;
|
||||||
|
}
|
||||||
|
|
||||||
if (interceptingPublishEventArgs != null)
|
if (interceptingPublishEventArgs != null)
|
||||||
{
|
{
|
||||||
pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
pubRecPacket.ReasonCode = (MqttPubRecReasonCode)(int)interceptingPublishEventArgs.Response.ReasonCode;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace MQTTnet.Formatter
|
|||||||
{
|
{
|
||||||
public struct ReadFixedHeaderResult
|
public struct ReadFixedHeaderResult
|
||||||
{
|
{
|
||||||
public static ReadFixedHeaderResult Cancelled { get; } = new ReadFixedHeaderResult
|
public static ReadFixedHeaderResult Canceled { get; } = new ReadFixedHeaderResult
|
||||||
{
|
{
|
||||||
IsCancelled = true
|
IsCanceled = true
|
||||||
};
|
};
|
||||||
|
|
||||||
public static ReadFixedHeaderResult ConnectionClosed { get; } = new ReadFixedHeaderResult
|
public static ReadFixedHeaderResult ConnectionClosed { get; } = new ReadFixedHeaderResult
|
||||||
@@ -16,7 +16,7 @@ namespace MQTTnet.Formatter
|
|||||||
IsConnectionClosed = true
|
IsConnectionClosed = true
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool IsCancelled { get; set; }
|
public bool IsCanceled { get; set; }
|
||||||
|
|
||||||
public bool IsConnectionClosed { get; set; }
|
public bool IsConnectionClosed { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -263,27 +264,15 @@ namespace MQTTnet.Implementations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
bool InternalUserCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||||
{
|
{
|
||||||
var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler;
|
var certificateValidationHandler = _tcpOptions?.TlsOptions?.CertificateValidationHandler;
|
||||||
if (certificateValidationHandler != null)
|
if (certificateValidationHandler != null)
|
||||||
{
|
{
|
||||||
var eventArgs = new MqttClientCertificateValidationEventArgs
|
var eventArgs = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _tcpOptions);
|
||||||
{
|
|
||||||
Certificate = x509Certificate,
|
|
||||||
Chain = chain,
|
|
||||||
SslPolicyErrors = sslPolicyErrors,
|
|
||||||
ClientOptions = _tcpOptions
|
|
||||||
};
|
|
||||||
|
|
||||||
return certificateValidationHandler(eventArgs);
|
return certificateValidationHandler(eventArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_tcpOptions?.TlsOptions?.IgnoreCertificateChainErrors ?? false)
|
|
||||||
{
|
|
||||||
sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sslPolicyErrors == SslPolicyErrors.None;
|
return sslPolicyErrors == SslPolicyErrors.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -224,14 +224,7 @@ namespace MQTTnet.Implementations
|
|||||||
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
|
||||||
{
|
{
|
||||||
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
|
// TODO: Find a way to add client options to same callback. Problem is that they have a different type.
|
||||||
var context = new MqttClientCertificateValidationEventArgs
|
var context = new MqttClientCertificateValidationEventArgs(certificate, chain, sslPolicyErrors, _options);
|
||||||
{
|
|
||||||
Certificate = certificate,
|
|
||||||
Chain = chain,
|
|
||||||
SslPolicyErrors = sslPolicyErrors,
|
|
||||||
ClientOptions = _options
|
|
||||||
};
|
|
||||||
|
|
||||||
return certificateValidationHandler(context);
|
return certificateValidationHandler(context);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,74 +11,15 @@ 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 Enqueue(TItem item)
|
|
||||||
{
|
|
||||||
lock (_syncRoot)
|
|
||||||
{
|
|
||||||
_queue.Enqueue(item);
|
|
||||||
_semaphore?.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
if (_queue.TryDequeue(out var item))
|
|
||||||
{
|
|
||||||
return new AsyncQueueDequeueResult<TItem>(true, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AsyncQueueDequeueResult<TItem>(false, default);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
|
Interlocked.Exchange(ref _queue, new ConcurrentQueue<TItem>());
|
||||||
@@ -88,9 +29,73 @@ namespace MQTTnet.Internal
|
|||||||
{
|
{
|
||||||
lock (_syncRoot)
|
lock (_syncRoot)
|
||||||
{
|
{
|
||||||
_semaphore?.Dispose();
|
_signal.Dispose();
|
||||||
_semaphore = null;
|
|
||||||
|
_isDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Enqueue(TItem item)
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_queue.Enqueue(item);
|
||||||
|
_signal.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncQueueDequeueResult<TItem> TryDequeue()
|
||||||
|
{
|
||||||
|
if (_queue.TryDequeue(out var item))
|
||||||
|
{
|
||||||
|
return AsyncQueueDequeueResult<TItem>.Success(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsyncQueueDequeueResult<TItem>.NonSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AsyncQueueDequeueResult<TItem>> TryDequeueAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task task = null;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
return AsyncQueueDequeueResult<TItem>.NonSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queue.IsEmpty)
|
||||||
|
{
|
||||||
|
task = _signal.WaitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
await task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
return AsyncQueueDequeueResult<TItem>.NonSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queue.TryDequeue(out var item))
|
||||||
|
{
|
||||||
|
return AsyncQueueDequeueResult<TItem>.Success(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return AsyncQueueDequeueResult<TItem>.NonSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AsyncQueueDequeueResult<TItem>.NonSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
namespace MQTTnet.Internal
|
namespace MQTTnet.Internal
|
||||||
{
|
{
|
||||||
public class AsyncQueueDequeueResult<TItem>
|
public sealed class AsyncQueueDequeueResult<TItem>
|
||||||
{
|
{
|
||||||
|
public static readonly AsyncQueueDequeueResult<TItem> NonSuccess = new AsyncQueueDequeueResult<TItem>(false, default);
|
||||||
|
|
||||||
public AsyncQueueDequeueResult(bool isSuccess, TItem item)
|
public AsyncQueueDequeueResult(bool isSuccess, TItem item)
|
||||||
{
|
{
|
||||||
IsSuccess = isSuccess;
|
IsSuccess = isSuccess;
|
||||||
@@ -15,5 +17,10 @@ namespace MQTTnet.Internal
|
|||||||
public bool IsSuccess { get; }
|
public bool IsSuccess { get; }
|
||||||
|
|
||||||
public TItem Item { get; }
|
public TItem Item { get; }
|
||||||
|
|
||||||
|
public static AsyncQueueDequeueResult<TItem> Success(TItem item)
|
||||||
|
{
|
||||||
|
return new AsyncQueueDequeueResult<TItem>(true, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
|
// If there is already a waiting task let it run.
|
||||||
|
if (_waiter != null)
|
||||||
|
{
|
||||||
|
_waiter.Approve();
|
||||||
|
_waiter = null;
|
||||||
|
|
||||||
|
// Since we already got a waiter the signal must be reset right now!
|
||||||
|
_isSignaled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WaitAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
ThrowIfDisposed();
|
||||||
|
|
||||||
|
Cleanup();
|
||||||
|
|
||||||
|
if (_isSignaled)
|
||||||
|
{
|
||||||
|
_isSignaled = false;
|
||||||
|
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 Cleanup()
|
||||||
|
{
|
||||||
|
// Cleanup if the previous waiter was cancelled.
|
||||||
|
if (_waiter != null && _waiter.Task.IsCanceled)
|
||||||
|
{
|
||||||
|
_waiter = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user