This commit is contained in:
Christian
2022-02-20 21:10:46 +01:00
committed by GitHub
parent 47e674e4d3
commit 2ce9b94ec9
608 changed files with 22364 additions and 19420 deletions
@@ -0,0 +1,93 @@
// 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;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Assigned_Client_ID_Tests : BaseTestClass
{
[TestMethod]
public Task Connect_With_No_Client_Id()
{
return Connect_With_Client_Id("test_123", "test_123", null, "test_123");
}
[TestMethod]
public Task Connect_With_Client_Id()
{
return Connect_With_Client_Id("Connect_With_Client_Id_test_456", null, "test_456", null);
}
async Task Connect_With_Client_Id(string expectedClientId, string expectedReturnedClientId, string usedClientId, string assignedClientId)
{
using (var testEnvironment = CreateTestEnvironment())
{
string serverConnectedClientId = null;
string serverDisconnectedClientId = null;
string clientAssignedClientId = null;
// Arrange server
var disconnectedMre = new ManualResetEventSlim();
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += e =>
{
if (string.IsNullOrEmpty(e.ClientId))
{
e.AssignedClientIdentifier = assignedClientId;
e.ReasonCode = MqttConnectReasonCode.Success;
}
return PlatformAbstractionLayer.CompletedTask;
};
testEnvironment.Server.ClientConnectedAsync += args =>
{
serverConnectedClientId = args.ClientId;
return PlatformAbstractionLayer.CompletedTask;
};
testEnvironment.Server.ClientDisconnectedAsync += args =>
{
serverDisconnectedClientId = args.ClientId;
disconnectedMre.Set();
return PlatformAbstractionLayer.CompletedTask;
};
// Arrange client
var client = testEnvironment.CreateClient();
client.ConnectedAsync += args =>
{
clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier;
return PlatformAbstractionLayer.CompletedTask;
};
// Act
await client.ConnectAsync(new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithClientId(usedClientId)
.Build());
await client.DisconnectAsync();
// Wait for ClientDisconnectedHandler to trigger
disconnectedMre.Wait(1000);
// Assert
Assert.AreEqual(expectedClientId, serverConnectedClientId);
Assert.AreEqual(expectedClientId, serverDisconnectedClientId);
Assert.AreEqual(expectedReturnedClientId, clientAssignedClientId);
}
}
}
}
@@ -0,0 +1,81 @@
// 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.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Implementations;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Connection_Tests : BaseTestClass
{
[TestMethod]
public async Task Close_Idle_Connection_On_Connect()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1)));
var client = new CrossPlatformSocket(AddressFamily.InterNetwork);
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
// Don't send anything. The server should close the connection.
await Task.Delay(TimeSpan.FromSeconds(3));
try
{
var receivedBytes = await client.ReceiveAsync(new ArraySegment<byte>(new byte[10]), SocketFlags.Partial);
if (receivedBytes == 0)
{
return;
}
Assert.Fail("Receive should throw an exception.");
}
catch (SocketException)
{
}
}
}
[TestMethod]
public async Task Send_Garbage()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1)));
// Send an invalid packet and ensure that the server will close the connection and stay in a waiting state
// forever. This is security related.
var client = new CrossPlatformSocket(AddressFamily.InterNetwork);
await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None);
var buffer = Encoding.UTF8.GetBytes("Garbage");
await client.SendAsync(new ArraySegment<byte>(buffer), SocketFlags.None);
await Task.Delay(TimeSpan.FromSeconds(3));
try
{
var receivedBytes = await client.ReceiveAsync(new ArraySegment<byte>(new byte[10]), SocketFlags.Partial);
if (receivedBytes == 0)
{
return;
}
Assert.Fail("Receive should throw an exception.");
}
catch (SocketException)
{
}
}
}
}
}
+197
View File
@@ -0,0 +1,197 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Events_Tests : BaseTestClass
{
[TestMethod]
public async Task Fire_Client_Connected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
ClientConnectedEventArgs eventArgs = null;
server.ClientConnectedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1"));
Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion);
Assert.AreEqual("TheUser", eventArgs.UserName);
}
}
[TestMethod]
public async Task Fire_Client_Disconnected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
ClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser"));
await client.DisconnectAsync();
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1"));
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Fire_Client_Subscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
ClientSubscribedTopicEventArgs eventArgs = null;
server.ClientSubscribedTopicAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce);
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic);
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel);
}
}
[TestMethod]
public async Task Fire_Client_Unsubscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
ClientUnsubscribedTopicEventArgs eventArgs = null;
server.ClientUnsubscribedTopicAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
await client.UnsubscribeAsync("The/Topic");
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter);
}
}
[TestMethod]
public async Task Fire_Application_Message_Received_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
InterceptingPublishEventArgs eventArgs = null;
server.InterceptingPublishAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
await client.PublishStringAsync("The_Topic", "The_Payload");
await LongTestDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event)));
Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic);
Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString());
}
}
[TestMethod]
public async Task Fire_Started_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = testEnvironment.CreateServer(new MqttServerOptions());
EventArgs eventArgs = null;
server.StartedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
await server.StartAsync();
await LongTestDelay();
Assert.IsNotNull(eventArgs);
}
}
[TestMethod]
public async Task Fire_Stopped_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
EventArgs eventArgs = null;
server.StoppedAsync += e =>
{
eventArgs = e;
return PlatformAbstractionLayer.CompletedTask;
};
await server.StopAsync();
await LongTestDelay();
Assert.IsNotNull(eventArgs);
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Server;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Injection_Tests : BaseTestClass
{
[TestMethod]
public async Task Inject_Application_Message_At_Session_Level()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var receiver1 = await testEnvironment.ConnectClient();
var receiver2 = await testEnvironment.ConnectClient();
var messageReceivedHandler1 = testEnvironment.CreateApplicationMessageHandler(receiver1);
var messageReceivedHandler2 = testEnvironment.CreateApplicationMessageHandler(receiver2);
var status = await server.GetSessionsAsync();
var clientStatus = status[0];
await receiver1.SubscribeAsync("#");
await receiver2.SubscribeAsync("#");
await clientStatus.EnqueueApplicationMessageAsync(new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build());
await LongTestDelay();
Assert.AreEqual(1, messageReceivedHandler1.ReceivedEventArgs.Count);
Assert.AreEqual("InjectedOne", messageReceivedHandler1.ReceivedEventArgs[0].ApplicationMessage.Topic);
// The second receiver should NOT receive the message.
Assert.AreEqual(0, messageReceivedHandler2.ReceivedEventArgs.Count);
}
}
[TestMethod]
public async Task Inject_ApplicationMessage_At_Server_Level()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var receiver = await testEnvironment.ConnectClient();
var messageReceivedHandler = testEnvironment.CreateApplicationMessageHandler(receiver);
await receiver.SubscribeAsync("#");
var injectedApplicationMessage = new MqttApplicationMessageBuilder().WithTopic("InjectedOne").Build();
await server.InjectApplicationMessage(new InjectedMqttApplicationMessage(injectedApplicationMessage));
await LongTestDelay();
Assert.AreEqual(1, messageReceivedHandler.ReceivedEventArgs.Count);
Assert.AreEqual("InjectedOne", messageReceivedHandler.ReceivedEventArgs[0].ApplicationMessage.Topic);
}
}
}
}
@@ -0,0 +1,68 @@
// 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;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class KeepAlive_Tests : BaseTestClass
{
[TestMethod]
public async Task Disconnect_Client_DueTo_KeepAlive()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectLowLevelClient(o => o
.WithTimeout(TimeSpan.FromSeconds(1))
.WithTimeout(TimeSpan.Zero)
.WithProtocolVersion(MqttProtocolVersion.V500)).ConfigureAwait(false);
await client.SendAsync(new MqttConnectPacket
{
CleanSession = true,
ClientId = "Disconnect_Client_DueTo_KeepAlive",
KeepAlivePeriod = 1
}, CancellationToken.None).ConfigureAwait(false);
var responsePacket = await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
Assert.IsTrue(responsePacket is MqttConnAckPacket);
for (var i = 0; i < 6; i++)
{
await Task.Delay(500);
await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None);
responsePacket = await client.ReceiveAsync(CancellationToken.None);
Assert.IsTrue(responsePacket is MqttPingRespPacket);
}
// If we reach this point everything works as expected (server did not close the connection
// due to proper ping messages.
// Now we will wait 1.1 seconds because the server MUST wait 1.5 seconds in total (See spec).
await Task.Delay(1100);
await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None);
responsePacket = await client.ReceiveAsync(CancellationToken.None);
Assert.IsTrue(responsePacket is MqttPingRespPacket);
// Now we will wait longer than 1.5 so that the server will close the connection.
responsePacket = await client.ReceiveAsync(CancellationToken.None);
var disconnectPacket = responsePacket as MqttDisconnectPacket;
Assert.IsTrue(disconnectPacket != null);
Assert.AreEqual(disconnectPacket.ReasonCode, MqttDisconnectReasonCode.KeepAliveTimeout);
}
}
}
}
+172
View File
@@ -0,0 +1,172 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Load_Tests : BaseTestClass
{
[TestMethod]
public async Task Handle_100_000_Messages_In_Receiving_Client()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var receivedMessages = 0;
using (var receiverClient = await testEnvironment.ConnectClient())
{
receiverClient.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
};
await receiverClient.SubscribeAsync("t/+");
for (var i = 0; i < 100; i++)
{
_ = Task.Run(
async () =>
{
using (var client = await testEnvironment.ConnectClient())
{
var applicationMessageBuilder = new MqttApplicationMessageBuilder();
for (var j = 0; j < 1000; j++)
{
var message = applicationMessageBuilder.WithTopic("t/" + j)
.Build();
await client.PublishAsync(message)
.ConfigureAwait(false);
}
await client.DisconnectAsync();
}
});
}
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(60));
Assert.AreEqual(100000, receivedMessages);
}
}
}
[TestMethod]
public async Task Handle_100_000_Messages_In_Low_Level_Client()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var receivedMessages = 0;
server.InterceptingPublishAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
};
for (var i = 0; i < 100; i++)
{
_ = Task.Run(
async () =>
{
try
{
using (var client = await testEnvironment.ConnectLowLevelClient())
{
await client.SendAsync(
new MqttConnectPacket
{
ClientId = "Handle_100_000_Messages_In_Low_Level_Client_" + Guid.NewGuid()
}, CancellationToken.None);
var packet = await client.ReceiveAsync(CancellationToken.None);
var connAckPacket = packet as MqttConnAckPacket;
Assert.IsTrue(connAckPacket != null);
Assert.AreEqual(MqttConnectReasonCode.Success, connAckPacket.ReasonCode);
var publishPacket = new MqttPublishPacket();
for (var j = 0; j < 1000; j++)
{
publishPacket.Topic = j.ToString();
await client.SendAsync(publishPacket, CancellationToken.None)
.ConfigureAwait(false);
}
await client.DisconnectAsync(CancellationToken.None);
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
});
}
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(10));
Assert.AreEqual(100000, receivedMessages);
}
}
[TestMethod]
public async Task Handle_100_000_Messages_In_Server()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
var receivedMessages = 0;
server.InterceptingPublishAsync += e =>
{
Interlocked.Increment(ref receivedMessages);
return PlatformAbstractionLayer.CompletedTask;
};
for (var i = 0; i < 100; i++)
{
_ = Task.Run(
async () =>
{
using (var client = await testEnvironment.ConnectClient())
{
var applicationMessageBuilder = new MqttApplicationMessageBuilder();
for (var j = 0; j < 1000; j++)
{
var message = applicationMessageBuilder.WithTopic(j.ToString())
.Build();
await client.PublishAsync(message)
.ConfigureAwait(false);
}
await client.DisconnectAsync();
}
});
}
SpinWait.SpinUntil(() => receivedMessages == 100000, TimeSpan.FromSeconds(10));
Assert.AreEqual(100000, receivedMessages);
}
}
}
}
@@ -0,0 +1,139 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class MqttSubscriptionsManager_Tests : BaseTestClass
{
MqttClientSubscriptionsManager _subscriptionsManager;
[TestMethod]
public async Task MqttSubscriptionsManager_SubscribeAndUnsubscribeSingle()
{
var sp = new MqttSubscribePacket
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()
}
};
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
Assert.IsTrue(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed);
var up = new MqttUnsubscribePacket();
up.TopicFilters.Add("A/B/C");
await _subscriptionsManager.Unsubscribe(up, CancellationToken.None);
Assert.IsFalse(CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed);
}
[TestMethod]
public async Task MqttSubscriptionsManager_SubscribeDifferentQoSSuccess()
{
var sp = new MqttSubscribePacket
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }
}
};
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, "");
Assert.IsTrue(result.IsSubscribed);
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod]
public async Task MqttSubscriptionsManager_SubscribeSingleNoSuccess()
{
var sp = new MqttSubscribePacket
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()
}
};
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
Assert.IsFalse(CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed);
}
[TestMethod]
public async Task MqttSubscriptionsManager_SubscribeSingleSuccess()
{
var sp = new MqttSubscribePacket
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()
}
};
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "");
Assert.IsTrue(result.IsSubscribed);
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce);
}
[TestMethod]
public async Task MqttSubscriptionsManager_SubscribeTwoTimesSuccess()
{
var sp = new MqttSubscribePacket
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce },
new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }
}
};
await _subscriptionsManager.Subscribe(sp, CancellationToken.None);
var result = CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, "");
Assert.IsTrue(result.IsSubscribed);
Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce);
}
[TestInitialize]
public void TestInitialize()
{
var logger = new TestLogger();
var options = new MqttServerOptions();
var retainedMessagesManager = new MqttRetainedMessagesManager(new MqttServerEventContainer(), logger);
var eventContainer = new MqttServerEventContainer();
var clientSessionManager = new MqttClientSessionsManager(options, retainedMessagesManager, eventContainer, logger);
var session = new MqttSession("", false, new ConcurrentDictionary<object, object>(), options, eventContainer, retainedMessagesManager, clientSessionManager);
_subscriptionsManager = new MqttClientSubscriptionsManager(session, new MqttServerEventContainer(), retainedMessagesManager, clientSessionManager);
}
CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel applicationMessageQoSLevel, string senderClientId)
{
ulong topicHashMask; // not needed
bool hasWildcard; // not needed
MqttSubscription.CalculateTopicHash(topic, out var topicHash, out topicHashMask, out hasWildcard);
return _subscriptionsManager.CheckSubscriptions(topic, topicHash, applicationMessageQoSLevel, senderClientId);
}
}
}
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class No_Local_Tests : BaseTestClass
{
[TestMethod]
public Task Subscribe_With_No_Local()
{
return ExecuteTest(true, 0);
}
[TestMethod]
public Task Subscribe_Without_No_Local()
{
return ExecuteTest(false, 1);
}
async Task ExecuteTest(
bool noLocal,
int expectedCountAfterPublish)
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
var topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic").WithNoLocal(noLocal).Build();
await client1.SubscribeAsync(topicFilter);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(0);
// The client will publish a message where it is itself subscribing to.
await client1.PublishStringAsync("Topic", "Payload", retain: true);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterPublish);
}
}
}
}
@@ -0,0 +1,48 @@
// 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.Formatter;
using MQTTnet.Client;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Retain_As_Published_Tests : BaseTestClass
{
[TestMethod]
public Task Subscribe_With_Retain_As_Published()
{
return ExecuteTest(true);
}
[TestMethod]
public Task Subscribe_Without_Retain_As_Published()
{
return ExecuteTest(false);
}
async Task ExecuteTest(bool retainAsPublished)
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
var topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainAsPublished(retainAsPublished).Build();
await client1.SubscribeAsync(topicFilter);
await LongTestDelay();
// The client will publish a message where it is itself subscribing to.
await client1.PublishStringAsync("Topic", "Payload", retain: true);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(1);
Assert.AreEqual(retainAsPublished, applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.Retain);
}
}
}
}
@@ -0,0 +1,70 @@
// 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 Retain_Handling_Tests : BaseTestClass
{
[TestMethod]
public Task Send_At_Subscribe()
{
return ExecuteTest(MqttRetainHandling.SendAtSubscribe, 1, 2, 3);
}
[TestMethod]
public Task Do_Not_Send_On_Subscribe()
{
return ExecuteTest(MqttRetainHandling.DoNotSendOnSubscribe, 0, 1, 1);
}
[TestMethod]
public Task Send_At_Subscribe_If_New_Subscription_Only()
{
return ExecuteTest(MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly, 1, 2, 2);
}
async Task ExecuteTest(
MqttRetainHandling retainHandling,
int expectedCountAfterSubscribe,
int expectedCountAfterSecondPublish,
int expectedCountAfterSecondSubscribe)
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
await client1.PublishStringAsync("Topic", "Payload", retain: true);
await LongTestDelay();
var client2 = await testEnvironment.ConnectClient();
var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client2);
var topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic").WithRetainHandling(retainHandling).Build();
await client2.SubscribeAsync(topicFilter);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSubscribe);
await client1.PublishStringAsync("Topic", "Payload", retain: true);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondPublish);
await client2.SubscribeAsync(topicFilter);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondSubscribe);
}
}
}
}
@@ -0,0 +1,177 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Retained_Messages_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V3()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.RetainAvailable);
}
}
[TestMethod]
public async Task Server_Reports_Retained_Messages_Supported_V5()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.RetainAvailable);
}
}
[TestMethod]
public async Task Retained_Messages_Flow()
{
using (var testEnvironment = CreateTestEnvironment())
{
var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build();
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c1.PublishAsync(retainedMessage);
await c1.DisconnectAsync();
await LongTestDelay();
for (var i = 0; i < 5; i++)
{
await c2.UnsubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i);
await c2.SubscribeAsync("r");
await Task.Delay(100);
messageHandler.AssertReceivedCountEquals(i + 1);
}
await c2.DisconnectAsync();
}
}
[TestMethod]
public async Task Receive_No_Retained_Message_After_Subscribe()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained_other").Build());
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
}
}
[TestMethod]
public async Task Receive_Retained_Message_After_Subscribe()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.DisconnectAsync();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build());
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(1);
Assert.IsTrue(messageHandler.ReceivedEventArgs.First().ApplicationMessage.Retain);
}
}
[TestMethod]
public async Task Clear_Retained_Message_With_Empty_Payload()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[0]).WithRetainFlag().Build());
await c1.DisconnectAsync();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
}
}
[TestMethod]
public async Task Clear_Retained_Message_With_Null_Payload()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build());
await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload((byte[])null).WithRetainFlag().Build());
await c1.DisconnectAsync();
var c2 = await testEnvironment.ConnectClient();
var messageHandler = testEnvironment.CreateApplicationMessageHandler(c2);
await Task.Delay(200);
await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce });
await Task.Delay(500);
messageHandler.AssertReceivedCountEquals(0);
}
}
}
}
@@ -0,0 +1,97 @@
// 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.Adapter;
using MQTTnet.Client;
using MQTTnet.Exceptions;
using MQTTnet.Implementations;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Security_Tests : BaseTestClass
{
[TestMethod]
public async Task Use_Username_Null_Password_Empty()
{
string username = null;
var password = string.Empty;
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(username, password)
.Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.IsInstanceOfType(ex.InnerException, typeof(MqttProtocolViolationException));
Assert.AreEqual("Error while authenticating. If the User Name Flag is set to 0, the Password Flag MUST be set to 0 [MQTT-3.1.2-22].", ex.Message, false);
Assert.AreEqual(MqttClientConnectResultCode.UnspecifiedError, ex.ResultCode);
}
}
[TestMethod]
public Task Handle_Wrong_UserName()
{
return TestCredentials("x", "Password1");
}
[TestMethod]
public Task Handle_Wrong_Password()
{
return TestCredentials("UserName", "x");
}
[TestMethod]
public Task Handle_Wrong_UserName_And_Password()
{
return TestCredentials("x", "x");
}
private async Task TestCredentials(string userName, string password)
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += e =>
{
if (e.Username != "UserName1")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
if (e.Password != "Password1")
{
e.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
}
return PlatformAbstractionLayer.CompletedTask;
};
var client = testEnvironment.CreateClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithCredentials(userName, password)
.Build();
var ex = await Assert.ThrowsExceptionAsync<MqttConnectingFailedException>(async () => await client.ConnectAsync(clientOptions));
Assert.AreEqual(MqttClientConnectResultCode.BadUserNameOrPassword, ex.Result.ResultCode);
}
}
}
}
@@ -0,0 +1,53 @@
// 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.Adapter;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Server_Reference_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_With_Reference_Server()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += e =>
{
e.ReasonCode = MqttConnectReasonCode.ServerMoved;
e.ServerReference = "new_server";
return PlatformAbstractionLayer.CompletedTask;
};
try
{
var client = testEnvironment.CreateClient();
await client.ConnectAsync(new MqttClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.Build());
Assert.Fail();
}
catch (MqttConnectingFailedException e)
{
Assert.AreEqual(MqttClientConnectResultCode.ServerMoved, e.ResultCode);
Assert.AreEqual("new_server", e.Result.ServerReference);
}
}
}
}
}
@@ -0,0 +1,317 @@
// 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.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
using MqttClient = MQTTnet.Client.MqttClient;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Session_Tests : BaseTestClass
{
[TestMethod]
public async Task Clean_Session_Persistence()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
// Create server with persistent sessions enabled
await testEnvironment.StartServer(o => o.WithPersistentSessions());
const string ClientId = "Client1";
// Create client with clean session and long session expiry interval
var client1 = await testEnvironment.ConnectClient(
o => o.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession() // start and end with clean session
.WithClientId(ClientId)
.Build());
// Disconnect; empty session should be removed from server
await client1.DisconnectAsync();
// Simulate some time delay between connections
await Task.Delay(1000);
// Reconnect the same client ID without clean session
var client2 = testEnvironment.CreateClient();
var options = testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.WithSessionExpiryInterval(9999) // not relevant for v311 but testing impact
.WithCleanSession(false) // see if there is a session
.WithClientId(ClientId)
.Build();
var result = await client2.ConnectAsync(options).ConfigureAwait(false);
await client2.DisconnectAsync();
// Session should NOT be present for MQTT v311 and initial CleanSession == true
Assert.IsTrue(!result.IsSessionPresent, "Session present");
}
}
[TestMethod]
public async Task Fire_Deleted_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
// Arrange client and server.
var server = await testEnvironment.StartServer(o => o.WithPersistentSessions(false));
var deletedEventFired = false;
server.SessionDeletedAsync += e =>
{
deletedEventFired = true;
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
// Act: Disconnect the client -> Event must be fired.
await client.DisconnectAsync();
await LongTestDelay();
// Assert that the event was fired properly.
Assert.IsTrue(deletedEventFired);
}
}
[TestMethod]
public async Task Get_Session_Items_In_Status()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += e =>
{
// Don't validate anything. Just set some session items.
e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask;
};
await testEnvironment.ConnectClient();
var sessionStatus = await testEnvironment.Server.GetSessionsAsync();
var session = sessionStatus.First();
Assert.AreEqual(true, session.Items["can_subscribe_x"]);
}
}
[TestMethod]
public async Task Handle_Parallel_Connection_Attempts()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
await testEnvironment.StartServer();
var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var hasReceive = false;
void OnReceive()
{
if (!hasReceive)
{
hasReceive = true;
}
}
// 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 connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
await Task.Delay(5000);
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishStringAsync("aaa", "1");
await Task.Delay(3000);
Assert.AreEqual(true, hasReceive);
}
}
[TestMethod]
public async Task Manage_Session_MaxParallel()
{
using (var testEnvironment = CreateTestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServer(serverOptions);
var options = new MqttClientOptionsBuilder().WithClientId("1");
var clients = await Task.WhenAll(Enumerable.Range(0, 10).Select(i => TryConnect(testEnvironment, options)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
}
}
[DataTestMethod]
[DataRow(MqttQualityOfServiceLevel.ExactlyOnce)]
[DataRow(MqttQualityOfServiceLevel.AtLeastOnce)]
public async Task Retry_If_Not_PubAck(MqttQualityOfServiceLevel qos)
{
long count = 0;
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer(o => o.WithPersistentSessions());
var publisher = await testEnvironment.ConnectClient();
var subscriber = await testEnvironment.ConnectClient(o => o.WithClientId(qos.ToString()).WithCleanSession(false));
subscriber.ApplicationMessageReceivedAsync += c =>
{
c.AutoAcknowledge = false;
++count;
Console.WriteLine("process");
return PlatformAbstractionLayer.CompletedTask;
};
await subscriber.SubscribeAsync("#", qos);
var pub = publisher.PublishStringAsync("a", null, qos);
await Task.Delay(100);
await subscriber.DisconnectAsync();
await subscriber.ConnectAsync(subscriber.Options);
await Task.Delay(100);
var res = await pub;
Assert.AreEqual(MqttClientPublishReasonCode.Success, res.ReasonCode);
Assert.AreEqual(2, count);
}
}
[TestMethod]
public async Task Set_Session_Item()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.ValidatingConnectionAsync += e =>
{
// Don't validate anything. Just set some session items.
e.SessionItems["can_subscribe_x"] = true;
e.SessionItems["default_payload"] = "Hello World";
return PlatformAbstractionLayer.CompletedTask;
};
server.InterceptingSubscriptionAsync += e =>
{
if (e.TopicFilter.Topic == "x")
{
if (e.SessionItems["can_subscribe_x"] as bool? == false)
{
e.Response.ReasonCode = MqttSubscribeReasonCode.ImplementationSpecificError;
}
}
return PlatformAbstractionLayer.CompletedTask;
};
server.InterceptingPublishAsync += e =>
{
e.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(e.SessionItems["default_payload"] as string ?? string.Empty);
return PlatformAbstractionLayer.CompletedTask;
};
string receivedPayload = null;
var client = await testEnvironment.ConnectClient();
client.ApplicationMessageReceivedAsync += e =>
{
receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
return PlatformAbstractionLayer.CompletedTask;
};
var subscribeResult = await client.SubscribeAsync("x");
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode);
var client2 = await testEnvironment.ConnectClient();
await client2.PublishStringAsync("x");
await Task.Delay(1000);
Assert.AreEqual("Hello World", receivedPayload);
}
}
async Task<MqttClient> ConnectAndSubscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
{
try
{
var sendClient = await testEnvironment.ConnectClient(options);
sendClient.ApplicationMessageReceivedAsync += e =>
{
onReceive();
return PlatformAbstractionLayer.CompletedTask;
};
using (var subscribeTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
{
await sendClient.SubscribeAsync("aaa", MqttQualityOfServiceLevel.AtMostOnce, subscribeTimeout.Token).ConfigureAwait(false);
}
return sendClient;
}
catch (Exception)
{
return null;
}
}
async Task<MqttClient> TryConnect(TestEnvironment testEnvironment, MqttClientOptionsBuilder options)
{
try
{
return await testEnvironment.ConnectClient(options);
}
catch
{
return null;
}
}
}
}
@@ -0,0 +1,50 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Shared_Subscriptions_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Shared_Subscriptions_Not_Supported()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsFalse(connectResult.SharedSubscriptionAvailable);
}
}
[TestMethod]
public async Task Subscription_Of_Shared_Subscription_Is_Denied()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
var subscribeResult = await client.SubscribeAsync("$share/A");
Assert.AreEqual(MqttClientSubscribeResultCode.SharedSubscriptionsNotSupported, subscribeResult.Items.First().ResultCode);
}
}
}
}
+163
View File
@@ -0,0 +1,163 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Status_Tests : BaseTestClass
{
[TestMethod]
public async Task Show_Client_And_Session_Statistics()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1"));
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2"));
await Task.Delay(500);
var clientStatus = await server.GetClientsAsync();
var sessionStatus = await server.GetSessionsAsync();
Assert.AreEqual(2, clientStatus.Count);
Assert.AreEqual(2, sessionStatus.Count);
Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId));
Assert.IsTrue(clientStatus.Any(s => s.Id == c2.Options.ClientId));
await c1.DisconnectAsync();
await c2.DisconnectAsync();
await Task.Delay(500);
clientStatus = await server.GetClientsAsync();
sessionStatus = await server.GetSessionsAsync();
Assert.AreEqual(0, clientStatus.Count);
Assert.AreEqual(0, sessionStatus.Count);
}
}
[TestMethod]
public async Task Disconnect_Client()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1"));
await Task.Delay(1000);
var clientStatus = await server.GetClientsAsync();
Assert.AreEqual(1, clientStatus.Count);
Assert.IsTrue(clientStatus.Any(s => s.Id == c1.Options.ClientId));
await clientStatus.First().DisconnectAsync();
await Task.Delay(500);
Assert.IsFalse(c1.IsConnected);
clientStatus = await server.GetClientsAsync();
Assert.AreEqual(0, clientStatus.Count);
}
}
[TestMethod]
public async Task Keep_Persistent_Session()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1").WithCleanSession(false));
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2").WithCleanSession(false));
await c1.DisconnectAsync();
await Task.Delay(500);
var clientStatus = await server.GetClientsAsync();
var sessionStatus = await server.GetSessionsAsync();
Assert.AreEqual(1, clientStatus.Count);
Assert.AreEqual(2, sessionStatus.Count);
await c2.DisconnectAsync();
await Task.Delay(500);
clientStatus = await server.GetClientsAsync();
sessionStatus = await server.GetSessionsAsync();
Assert.AreEqual(0, clientStatus.Count);
Assert.AreEqual(2, sessionStatus.Count);
}
}
[TestMethod]
public async Task Track_Sent_Application_Messages()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient();
for (var i = 1; i < 25; i++)
{
await c1.PublishStringAsync("a");
await Task.Delay(50);
var clientStatus = await server.GetClientsAsync();
Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount);
Assert.AreEqual(0, clientStatus.First().ReceivedApplicationMessagesCount);
}
}
}
[TestMethod]
public async Task Track_Sent_Packets()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions());
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive());
for (var i = 1; i < 25; i++)
{
// At most once will send one packet to the client and the server will reply
// with an additional ACK packet.
await c1.PublishStringAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce);
await Task.Delay(500);
var clientStatus = await server.GetClientsAsync();
Assert.AreEqual(i, clientStatus.First().SentApplicationMessagesCount, "SAMC invalid!");
// + 1 because CONNECT is also counted.
Assert.AreEqual(i + 1, clientStatus.First().SentPacketsCount, "SPC invalid!");
// +2 because ConnACK + PubAck package is already counted.
Assert.AreEqual(i + 2, clientStatus.First().ReceivedPacketsCount, "RPC invalid!");
}
}
}
}
}
@@ -0,0 +1,317 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Subscribe_Tests : BaseTestClass
{
[TestMethod]
public async Task Intercept_Subscription()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServer();
server.InterceptingSubscriptionAsync += e =>
{
// Set the topic to "a" regards what the client wants to subscribe.
e.TopicFilter.Topic = "a";
return PlatformAbstractionLayer.CompletedTask;
};
var topicAReceived = false;
var topicBReceived = false;
var client = await testEnvironment.ConnectClient();
client.ApplicationMessageReceivedAsync += e =>
{
if (e.ApplicationMessage.Topic == "a")
{
topicAReceived = true;
}
else if (e.ApplicationMessage.Topic == "b")
{
topicBReceived = true;
}
return PlatformAbstractionLayer.CompletedTask;
};
await client.SubscribeAsync("b");
await client.PublishStringAsync("a");
await Task.Delay(500);
Assert.IsTrue(topicAReceived);
Assert.IsFalse(topicBReceived);
}
}
[TestMethod]
public async Task Subscribe_Unsubscribe()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
var server = await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c1"));
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
};
var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2"));
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce).Build();
await c2.PublishAsync(message);
await Task.Delay(500);
Assert.AreEqual(0, receivedMessagesCount);
var subscribeEventCalled = false;
server.ClientSubscribedTopicAsync += e =>
{
subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask;
};
await c1.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce });
await Task.Delay(250);
Assert.IsTrue(subscribeEventCalled, "Subscribe event not called.");
await c2.PublishAsync(message);
await Task.Delay(250);
Assert.AreEqual(1, receivedMessagesCount);
var unsubscribeEventCalled = false;
server.ClientUnsubscribedTopicAsync += e =>
{
unsubscribeEventCalled = e.TopicFilter == "a" && e.ClientId == c1.Options.ClientId;
return PlatformAbstractionLayer.CompletedTask;
};
await c1.UnsubscribeAsync("a");
await Task.Delay(250);
Assert.IsTrue(unsubscribeEventCalled, "Unsubscribe event not called.");
await c2.PublishAsync(message);
await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount);
await Task.Delay(500);
Assert.AreEqual(1, receivedMessagesCount);
}
}
[TestMethod]
public async Task Subscribe_Multiple_In_Single_Request()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
};
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter("a")
.WithTopicFilter("b")
.WithTopicFilter("c")
.Build());
var c2 = await testEnvironment.ConnectClient();
await c2.PublishStringAsync("a");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 1);
await c2.PublishStringAsync("b");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 2);
await c2.PublishStringAsync("c");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 3);
}
}
[TestMethod]
public async Task Subscribe_Lots_In_Single_Request()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
};
var optionsBuilder = new MqttClientSubscribeOptionsBuilder();
for (var i = 0; i < 500; i++)
{
optionsBuilder.WithTopicFilter(i.ToString());
}
await c1.SubscribeAsync(optionsBuilder.Build()).ConfigureAwait(false);
var c2 = await testEnvironment.ConnectClient();
var messageBuilder = new MqttApplicationMessageBuilder();
for (var i = 0; i < 500; i++)
{
messageBuilder.WithTopic(i.ToString());
await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false);
}
SpinWait.SpinUntil(() => receivedMessagesCount == 500, TimeSpan.FromSeconds(20));
Assert.AreEqual(500, receivedMessagesCount);
}
}
[TestMethod]
public async Task Subscribe_Lots_In_Multiple_Requests()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
};
for (var i = 0; i < 500; i++)
{
var so = new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter(i.ToString()).Build();
await c1.SubscribeAsync(so).ConfigureAwait(false);
await Task.Delay(10);
}
var c2 = await testEnvironment.ConnectClient();
var messageBuilder = new MqttApplicationMessageBuilder();
for (var i = 0; i < 500; i++)
{
messageBuilder.WithTopic(i.ToString());
await c2.PublishAsync(messageBuilder.Build()).ConfigureAwait(false);
await Task.Delay(10);
}
SpinWait.SpinUntil(() => receivedMessagesCount == 500, 5000);
Assert.AreEqual(500, receivedMessagesCount);
}
}
[TestMethod]
public async Task Subscribe_Multiple_In_Multiple_Request()
{
using (var testEnvironment = CreateTestEnvironment())
{
var receivedMessagesCount = 0;
await testEnvironment.StartServer();
var c1 = await testEnvironment.ConnectClient();
c1.ApplicationMessageReceivedAsync += e =>
{
Interlocked.Increment(ref receivedMessagesCount);
return PlatformAbstractionLayer.CompletedTask;
};
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter("a")
.Build());
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter("b")
.Build());
await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder()
.WithTopicFilter("c")
.Build());
var c2 = await testEnvironment.ConnectClient();
await c2.PublishStringAsync("a");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 1);
await c2.PublishStringAsync("b");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 2);
await c2.PublishStringAsync("c");
await Task.Delay(100);
Assert.AreEqual(receivedMessagesCount, 3);
}
}
[TestMethod]
public async Task Deny_Invalid_Topic()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
var server = await testEnvironment.StartServer();
server.InterceptingSubscriptionAsync += e =>
{
if (e.TopicFilter.Topic == "not_allowed_topic")
{
e.Response.ReasonCode = MqttSubscribeReasonCode.TopicFilterInvalid;
}
return PlatformAbstractionLayer.CompletedTask;
};
var client = await testEnvironment.ConnectClient();
var subscribeResult =await client.SubscribeAsync("allowed_topic");
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items.First().ResultCode);
subscribeResult =await client.SubscribeAsync("not_allowed_topic");
Assert.AreEqual(MqttClientSubscribeResultCode.TopicFilterInvalid, subscribeResult.Items.First().ResultCode);
}
}
}
}
@@ -0,0 +1,93 @@
// 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;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Subscription_Identifier_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Subscription_Identifiers_Supported()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.SubscriptionIdentifiersAvailable);
}
}
[TestMethod]
public async Task Subscribe_With_Subscription_Identifier()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
var topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic").Build();
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build();
await client1.SubscribeAsync(subscribeOptions);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(0);
// The client will publish a message where it is itself subscribing to.
await client1.PublishStringAsync("Topic", "Payload", retain: true);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(1);
applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456);
}
}
[TestMethod]
public async Task Subscribe_With_Multiple_Subscription_Identifiers()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
var applicationMessageHandler = testEnvironment.CreateApplicationMessageHandler(client1);
var topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic/A").Build();
var subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(456).WithTopicFilter(topicFilter).Build();
await client1.SubscribeAsync(subscribeOptions);
await LongTestDelay();
topicFilter = testEnvironment.Factory.CreateTopicFilterBuilder().WithTopic("Topic/+").Build();
subscribeOptions = testEnvironment.Factory.CreateSubscribeOptionsBuilder().WithSubscriptionIdentifier(789).WithTopicFilter(topicFilter).Build();
await client1.SubscribeAsync(subscribeOptions);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(0);
// The client will publish a message where it is itself subscribing to.
await client1.PublishStringAsync("Topic/A", "Payload", retain: true);
await LongTestDelay();
applicationMessageHandler.AssertReceivedCountEquals(1);
applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(456);
applicationMessageHandler.ReceivedEventArgs[0].ApplicationMessage.SubscriptionIdentifiers.Contains(789);
}
}
}
}
@@ -0,0 +1,612 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public class Subscription_TopicHash_Tests
{
enum TopicHashSelector { NoWildcard, SingleWildcard, MultiWildcard };
MQTTnet.Server.MqttSession _clientSession;
[TestMethod]
public void Match_Hash_Test_SingleWildCard()
{
var l0 = "pub0";
var l1 = "topic1";
var l2 = "+";
var l3 = "prop1";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
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);
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");
// The mask should have zeroes where the wildcard and ff at the end
var hashMaskBytes = GetBytes(topicHashMask);
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
Assert.AreEqual(hashMaskBytes[2], 0, "mask 2 mismatch");
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
}
[TestMethod]
public void Match_Hash_Test_MultiWildCard()
{
var l0 = "pub0";
var l1 = "topic1";
var l2 = "sub1";
var l3 = "#";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
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);
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
Assert.AreNotEqual(hashBytes[2], 0, "checksum 2 mismatch");
Assert.AreEqual(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");
// The mask should have zeroes where the wildcard is and zero onward
var hashMaskBytes = GetBytes(topicHashMask);
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch");
Assert.AreEqual(hashMaskBytes[3], 0, "mask 3 mismatch");
Assert.AreEqual(hashMaskBytes[4], 0, "mask 4 mismatch");
Assert.AreEqual(hashMaskBytes[5], 0, "mask 5 mismatch");
Assert.AreEqual(hashMaskBytes[6], 0, "mask 6 mismatch");
Assert.AreEqual(hashMaskBytes[7], 0, "mask 7 mismatch");
}
[TestMethod]
public void Match_Hash_Test_NoWildCard()
{
var l0 = "pub0";
var l1 = "topic1";
var l2 = "sub1";
var l3 = "prop1";
var topic = string.Format("{0}/{1}/{2}/{3}", l0, l1, l2, l3);
UInt64 topicHash;
UInt64 topicHashMask;
bool topicHasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out topicHashMask, out topicHasWildcard);
Assert.IsFalse(topicHasWildcard, "Wildcard detected when not wildcard present");
var hashBytes = GetBytes(topicHash);
Assert.AreNotEqual(hashBytes[0], 0, "checksum 0 mismatch");
Assert.AreNotEqual(hashBytes[1], 0, "checksum 1 mismatch");
Assert.AreNotEqual(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");
// The mask should have ff
var hashMaskBytes = GetBytes(topicHashMask);
Assert.AreEqual(hashMaskBytes[0], 0xff, "mask 0 mismatch");
Assert.AreEqual(hashMaskBytes[1], 0xff, "mask 1 mismatch");
Assert.AreEqual(hashMaskBytes[2], 0xff, "mask 2 mismatch");
Assert.AreEqual(hashMaskBytes[3], 0xff, "mask 3 mismatch");
Assert.AreEqual(hashMaskBytes[4], 0xff, "mask 4 mismatch");
Assert.AreEqual(hashMaskBytes[5], 0xff, "mask 5 mismatch");
Assert.AreEqual(hashMaskBytes[6], 0xff, "mask 6 mismatch");
Assert.AreEqual(hashMaskBytes[7], 0xff, "mask 7 mismatch");
}
/// <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
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]
public async Task Match_Hash_Test_Search_NoWildcard()
{
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.NoWildcard);
// all match lookup
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "No Wildcard All Match");
Assert.AreEqual(topics.Count, matchCount, "Not all topics matched");
// no match lookup
{
{
var topicsToFind = new List<string>();
foreach (var t in topics)
{
topicsToFind.Add(t + "x");
}
matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Append X)");
Assert.AreEqual(0, matchCount, "Topic match count not zero");
}
{
var topicsToFind = new List<string>();
foreach (var t in topics)
{
// replace last letter with x
topicsToFind.Add(t.Substring(0, t.Length - 1) + "x");
}
matchCount = CheckTopicSubscriptions(_clientSession, topicsToFind, "No Wildcard No Match (Replace X)");
Assert.AreEqual(0, matchCount, "Topic match count not zero");
}
}
}
[TestMethod]
public async Task Match_Hash_Test_Search_SingleWildcard()
{
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.SingleWildcard);
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Single Wildcard");
// Should match all topics
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
}
[TestMethod]
public async Task Match_Hash_Test_Search_MultiWildcard()
{
var topics = await PrepareTopicHashSubscriptions(TopicHashSelector.MultiWildcard);
var matchCount = CheckTopicSubscriptions(_clientSession, topics, "Multi Wildcard");
// Should match all topics
Assert.AreEqual(topics.Count, matchCount, "Topics not matched");
}
/// <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()
{
Dictionary<string, List<string>> topicsByPublisher;
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
const int NumPublishers = 5000;
const int NumTopicsPerPublisher = 10;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
// There will be many 'similar' topics ending with, i.e. "sensor100", "sensor101", ...
// Hash bucket depths should remain low.
var bucketDepths = new Dictionary<UInt64, int>();
int maxBucketDepth = 0;
UInt64 maxBucketDepthHash = 0;
var topicsByHash = new Dictionary<UInt64, List<string>>();
foreach (var t in topicsByPublisher)
{
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)
{
ulong topicHash;
ulong hashMask;
bool hasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash(topic, out topicHash, out hashMask, out hasWildcard);
Console.WriteLine();
Console.WriteLine("Topic: " + topic);
Console.WriteLine(string.Format("Hash: {0:X8}", topicHash));
Console.WriteLine(string.Format("Hash Mask: {0:X8}", hashMask));
Assert.AreEqual(expectedHash, topicHash, "Topic hash not as expected. Has the hash function changed?");
Assert.AreEqual(expectedHashMask, hashMask, "Topic hash mask not as expected");
}
async Task<List<string>> PrepareTopicHashSubscriptions(TopicHashSelector selector)
{
Dictionary<string, List<string>> topicsByPublisher;
Dictionary<string, List<string>> singleWildcardTopicsByPublisher;
Dictionary<string, List<string>> multiWildcardTopicsByPublisher;
const int NumPublishers = 1;
const int NumTopicsPerPublisher = 10000;
TopicGenerator.Generate(NumPublishers, NumTopicsPerPublisher, out topicsByPublisher, out singleWildcardTopicsByPublisher, out multiWildcardTopicsByPublisher);
var topics = topicsByPublisher.FirstOrDefault().Value;
var singleWildcardTopics = singleWildcardTopicsByPublisher.FirstOrDefault().Value;
var multiWildcardTopics = multiWildcardTopicsByPublisher.FirstOrDefault().Value;
const string ClientId = "Client1";
var logger = new Mockups.TestLogger();
var serverOptions = new MQTTnet.Server.MqttServerOptions();
var eventContainer = new MQTTnet.Server.MqttServerEventContainer();
var retainedMessagesManager = new MqttRetainedMessagesManager(eventContainer, logger);
var sessionManager = new MqttClientSessionsManager(serverOptions, retainedMessagesManager, eventContainer, logger);
_clientSession = new MQTTnet.Server.MqttSession(
ClientId,
false,
new Dictionary<object, object>(),
serverOptions,
eventContainer,
retainedMessagesManager,
sessionManager
);
List<string> topicsToSubscribe;
switch (selector)
{
case TopicHashSelector.SingleWildcard:
topicsToSubscribe = singleWildcardTopics;
break;
case TopicHashSelector.MultiWildcard:
topicsToSubscribe = multiWildcardTopics;
break;
default:
topicsToSubscribe = topics;
break;
}
foreach (var t in topicsToSubscribe)
{
var subPacket = new Packets.MqttSubscribePacket();
var filter = new Packets.MqttTopicFilter();
filter.Topic = t;
subPacket.TopicFilters.Add(filter);
await _clientSession.SubscriptionsManager.Subscribe(subPacket, default(CancellationToken));
}
return topics;
}
int CheckTopicSubscriptions(MQTTnet.Server.MqttSession clientSession, List<string> topicsToFind, string subject)
{
var matchCount = 0;
{
int resultCount = 0;
var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
var countUp = 0;
var countDown = topicsToFind.Count - 1;
for (; countUp < topicsToFind.Count; ++countUp, --countDown)
{
var topicToFind = topicsToFind[countDown];
UInt64 topicHash;
UInt64 hashMask;
bool hasWildcard;
MQTTnet.Server.MqttSubscription.CalculateTopicHash((string)topicToFind, out topicHash, out hashMask, out hasWildcard);
var result = clientSession.SubscriptionsManager.CheckSubscriptions((string)topicToFind, topicHash, MqttQualityOfServiceLevel.AtMostOnce, "OtherClient");
if (result.IsSubscribed)
{
++matchCount;
}
++resultCount;
}
stopWatch.Stop();
Console.Write("Match count: " + matchCount + "; ");
Console.WriteLine(subject + " lookup milliseconds: " + stopWatch.ElapsedMilliseconds);
}
return matchCount;
}
byte[] GetBytes(UInt64 value)
{
var bytes = BitConverter.GetBytes(value);
// Ensure that highest byte comes first for comparison left to right
if (BitConverter.IsLittleEndian)
return bytes.Reverse().ToArray();
return bytes;
}
}
}
@@ -0,0 +1,81 @@
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Topic_Alias_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Topic_Alias_Supported()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort)
.Build());
Assert.AreEqual(connectResult.TopicAliasMaximum, ushort.MaxValue);
}
}
[TestMethod]
public async Task Publish_With_Topic_Alias()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var receivedTopics = new List<string>();
var c1 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500));
c1.ApplicationMessageReceivedAsync += e =>
{
lock (receivedTopics)
{
receivedTopics.Add(e.ApplicationMessage.Topic);
}
return PlatformAbstractionLayer.CompletedTask;
};
await c1.SubscribeAsync("#");
var c2 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500));
var message = new MqttApplicationMessage
{
Topic = "this_is_the_topic",
TopicAlias = 22
};
await c2.PublishAsync(message);
message.Topic = null;
await c2.PublishAsync(message);
await c2.PublishAsync(message);
await Task.Delay(500);
Assert.AreEqual(3, receivedTopics.Count);
CollectionAssert.AllItemsAreNotNull(receivedTopics);
Assert.IsTrue(receivedTopics.All(t => t.Equals("this_is_the_topic")));
}
}
}
}
@@ -0,0 +1,65 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Server
{
[TestClass]
public class Feature_Tests
{
public TestContext TestContext { get; set; }
[TestMethod]
public async Task Use_User_Properties()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500));
var message = new MqttApplicationMessageBuilder()
.WithTopic("A")
.WithUserProperty("x", "1")
.WithUserProperty("y", "2")
.WithUserProperty("z", "3")
.WithUserProperty("z", "4"); // z is here two times to test list of items
await receiver.SubscribeAsync(new MqttClientSubscribeOptions
{
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilter { Topic = "#" }
}
}, CancellationToken.None);
MqttApplicationMessage receivedMessage = null;
receiver.ApplicationMessageReceivedAsync += e =>
{
receivedMessage = e.ApplicationMessage;
return PlatformAbstractionLayer.CompletedTask;
};
await sender.PublishAsync(message.Build(), CancellationToken.None);
await Task.Delay(1000);
Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(4, receivedMessage.UserProperties.Count);
}
}
}
}
@@ -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.
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Formatter;
namespace MQTTnet.Tests.Server
{
[TestClass]
public sealed class Wildcard_Subscription_Available_Tests : BaseTestClass
{
[TestMethod]
public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V3()
{
using (var testEnvironment = CreateTestEnvironment())
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V311)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.WildcardSubscriptionAvailable);
}
}
[TestMethod]
public async Task Server_Reports_Wildcard_Subscription_Available_Tests_Supported_V5()
{
using (var testEnvironment = CreateTestEnvironment(MqttProtocolVersion.V500))
{
await testEnvironment.StartServer();
var client = testEnvironment.CreateClient();
var connectResult = await client.ConnectAsync(testEnvironment.Factory.CreateClientOptionsBuilder()
.WithProtocolVersion(MqttProtocolVersion.V500)
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(connectResult.WildcardSubscriptionAvailable);
}
}
}
}