diff --git a/Build/MQTTnet.nuspec b/Build/MQTTnet.nuspec index fa364d50..dc54c49d 100644 --- a/Build/MQTTnet.nuspec +++ b/Build/MQTTnet.nuspec @@ -12,15 +12,21 @@ true MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker) and supports v3.1.0, v3.1.1 and v5.0.0 of the MQTT protocol. +* [Core] Added all builders to the MQTT factory. +* [Client] Renamed _MqttClientConnectResult_ to _MqttClientConnectResult_ (BREAKING CHANGE!). * [ManagedClient] Extended ReconnectAsync (thanks to @nvsnkv, #1202). -* [ManagedClient] Improved Amazon AWS support (thanks to @scottbrogden-iheartmedia, #1209). +* [ManagedClient] Improved Amazon AWS support (thanks to @scottbrogden-iheartmedia, #1209). +* [ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh). +* [Server] Added support for _RetainHandling_ (MQTTv5, BREAKING CHANGE!). +* [Server] Added support for _NoLocal_ (MQTTv5, BREAKING CHANGE!). +* [Server] Added support for _SubscriptionIdentifier_ (MQTTv5, BREAKING CHANGE!). +* [Server] Server now reports supported features properly after successful connection (MQTTv5, BREAKING CHANGE!). * [Server] Fixed a memory/performance leak when using QoS Level 1. * [Server] Exposed connection timestamp in client status. * [Server] Refactored connection management code. * [Server] Exposed more details in _MqttServerClientConnectedEventArgs_. * [Server] Processing all pending messages before stopping (thanks to @AblEdge, #1234). * [MQTTnet.Server] Moved server project to a dedicated GitHub repository. -* [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh). Git commit: $gitCommit Copyright Christian Kratky 2016-2021 diff --git a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs index 453021f3..6450f2cf 100644 --- a/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs +++ b/Source/MQTTnet.Extensions.Rpc/MqttRpcClient.cs @@ -95,12 +95,12 @@ namespace MQTTnet.Extensions.Rpc try { #if NET452 - var promise = new TaskCompletionSource(); + var awaitable = new TaskCompletionSource(); #else - var promise = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var awaitable = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); #endif - if (!_waitingCalls.TryAdd(responseTopic, promise)) + if (!_waitingCalls.TryAdd(responseTopic, awaitable)) { throw new InvalidOperationException(); } @@ -112,9 +112,9 @@ namespace MQTTnet.Extensions.Rpc await _mqttClient.SubscribeAsync(subscribeOptions, cancellationToken).ConfigureAwait(false); await _mqttClient.PublishAsync(requestMessage, cancellationToken).ConfigureAwait(false); - using (cancellationToken.Register(() => { promise.TrySetCanceled(); })) + using (cancellationToken.Register(() => { awaitable.TrySetCanceled(); })) { - return await promise.Task.ConfigureAwait(false); + return await awaitable.Task.ConfigureAwait(false); } } finally @@ -127,15 +127,15 @@ namespace MQTTnet.Extensions.Rpc Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) { - if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var promise)) + if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out var awaitable)) { return PlatformAbstractionLayer.CompletedTask; } #if NET452 - Task.Run(() => promise.TrySetResult(eventArgs.ApplicationMessage.Payload)); + Task.Run(() => awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload)); #else - promise.TrySetResult(eventArgs.ApplicationMessage.Payload); + awaitable.TrySetResult(eventArgs.ApplicationMessage.Payload); #endif // Set this message to handled to that other code can avoid execution etc. diff --git a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs index 447aaa69..a465bb66 100644 --- a/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs +++ b/Source/MQTTnet/Adapter/MqttConnectingFailedException.cs @@ -6,13 +6,13 @@ namespace MQTTnet.Adapter { public sealed class MqttConnectingFailedException : MqttCommunicationException { - public MqttConnectingFailedException(string message, Exception innerException, MqttClientAuthenticateResult authenticateResult) + public MqttConnectingFailedException(string message, Exception innerException, MqttClientConnectResult connectResult) : base(message, innerException) { - Result = authenticateResult; + Result = connectResult; } - public MqttClientAuthenticateResult Result { get; } + public MqttClientConnectResult Result { get; } public MqttClientConnectResultCode ResultCode => Result?.ResultCode ?? MqttClientConnectResultCode.UnspecifiedError; } diff --git a/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs deleted file mode 100644 index ecb1293c..00000000 --- a/Source/MQTTnet/Client/Connecting/MqttClientAuthenticateResult.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Generic; -using MQTTnet.Packets; -using MQTTnet.Protocol; - -namespace MQTTnet.Client.Connecting -{ - // TODO: Consider renaming this to _MqttClientConnectResult_ - public class MqttClientAuthenticateResult - { - /// - /// Gets or sets the result code. - /// Hint: MQTT 5 feature only. - /// - public MqttClientConnectResultCode ResultCode { get; set; } - - public bool IsSessionPresent { get; set; } - - public bool? WildcardSubscriptionAvailable { get; set; } - - public bool? RetainAvailable { get; set; } - - public string AssignedClientIdentifier { get; set; } - - /// - /// Gets or sets the authentication method. - /// Hint: MQTT 5 feature only. - /// - public string AuthenticationMethod { get; set; } - - /// - /// Gets or sets the authentication data. - /// Hint: MQTT 5 feature only. - /// - public byte[] AuthenticationData { get; set; } - - public uint? MaximumPacketSize { get; set; } - - /// - /// Gets or sets the reason string. - /// Hint: MQTT 5 feature only. - /// - public string ReasonString { get; set; } - - public ushort? ReceiveMaximum { get; set; } - - public MqttQualityOfServiceLevel MaximumQoS { get; set; } - - /// - /// Gets or sets the response information. - /// Hint: MQTT 5 feature only. - /// - public string ResponseInformation { get; set; } - - public ushort? TopicAliasMaximum { get; set; } - - public string ServerReference { get; set; } - - public ushort? ServerKeepAlive { get; set; } - - public uint? SessionExpiryInterval { get; set; } - - /// - /// Gets or sets a value indicating whether the subscription identifiers are available or not. - /// Hint: MQTT 5 feature only. - /// - public bool? SubscriptionIdentifiersAvailable { get; set; } - - /// - /// Gets or sets a value indicating whether the shared subscriptions are available or not. - /// Hint: MQTT 5 feature only. - /// - public bool? SharedSubscriptionAvailable { get; set; } - - /// - /// 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. - /// 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. - /// The feature is very similar to the HTTP header concept. - /// Hint: MQTT 5 feature only. - /// - public List UserProperties { get; set; } - } -} diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs new file mode 100644 index 00000000..16437932 --- /dev/null +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectResult.cs @@ -0,0 +1,115 @@ +using System.Collections.Generic; +using MQTTnet.Packets; +using MQTTnet.Protocol; + +namespace MQTTnet.Client.Connecting +{ + public sealed class MqttClientConnectResult + { + /// + /// Gets the result code. + /// MQTTv5 only. + /// + public MqttClientConnectResultCode ResultCode { get; internal set; } + + /// + /// Gets a value indicating whether a session was already available or not. + /// MQTTv5 only. + /// + public bool IsSessionPresent { get; internal set; } + + /// + /// Gets a value indicating whether wildcards can be used in subscriptions at the current server. + /// MQTTv5 only. + /// + public bool WildcardSubscriptionAvailable { get; internal set; } + + /// + /// Gets whether the server supports retained messages. + /// MQTTv5 only. + /// + public bool RetainAvailable { get; internal set; } + + /// + /// Gets the client identifier which was chosen by the server. + /// MQTTv5 only. + /// + public string AssignedClientIdentifier { get; internal set; } + + /// + /// Gets the authentication method. + /// MQTTv5 only. + /// + public string AuthenticationMethod { get; internal set; } + + /// + /// Gets the authentication data. + /// MQTTv5 only. + /// + public byte[] AuthenticationData { get; internal set; } + + public uint? MaximumPacketSize { get; internal set; } + + /// + /// Gets the reason string. + /// MQTTv5 only. + /// + public string ReasonString { get; internal set; } + + public ushort? ReceiveMaximum { get; internal set; } + + /// + /// Gets the maximum QoS which is supported by the server. + /// MQTTv5 only. + /// + public MqttQualityOfServiceLevel MaximumQoS { get; internal set; } + + /// + /// Gets the response information. + /// MQTTv5 only. + /// + public string ResponseInformation { get; internal set; } + + /// + /// Gets the maximum value for a topic alias. 0 means not supported. + /// MQTTv5 only. + /// + public ushort TopicAliasMaximum { get; internal set; } + + /// + /// Gets an alternate server which should be used instead of the current one. + /// MQTTv5 only. + /// + public string ServerReference { get; internal set; } + + /// + /// Gets the keep alive interval which was chosen by the server instead of the + /// keep alive interval from the client CONNECT packet. + /// MQTTv5 only. + /// + public ushort? ServerKeepAlive { get; internal set; } + + public uint? SessionExpiryInterval { get; internal set; } + + /// + /// Gets a value indicating whether the subscription identifiers are available or not. + /// MQTTv5 only. + /// + public bool SubscriptionIdentifiersAvailable { get; internal set; } + + /// + /// Gets a value indicating whether the shared subscriptions are available or not. + /// MQTTv5 only. + /// + public bool SharedSubscriptionAvailable { get; internal set; } + + /// + /// Gets 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. + /// 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. + /// The feature is very similar to the HTTP header concept. + /// MQTTv5 only. + /// + public List UserProperties { get; internal set; } + } +} diff --git a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs index 02cd47f5..dc65e271 100644 --- a/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Connecting/MqttClientConnectedEventArgs.cs @@ -4,15 +4,15 @@ namespace MQTTnet.Client.Connecting { public class MqttClientConnectedEventArgs : EventArgs { - public MqttClientConnectedEventArgs(MqttClientAuthenticateResult authenticateResult) + public MqttClientConnectedEventArgs(MqttClientConnectResult connectResult) { - AuthenticateResult = authenticateResult ?? throw new ArgumentNullException(nameof(authenticateResult)); + ConnectResult = connectResult ?? throw new ArgumentNullException(nameof(connectResult)); } /// /// Gets the authentication result. /// Hint: MQTT 5 feature only. /// - public MqttClientAuthenticateResult AuthenticateResult { get; } + public MqttClientConnectResult ConnectResult { get; } } } diff --git a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs index 932f009c..f4a47315 100644 --- a/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs +++ b/Source/MQTTnet/Client/Disconnecting/MqttClientDisconnectedEventArgs.cs @@ -5,11 +5,11 @@ namespace MQTTnet.Client.Disconnecting { public sealed class MqttClientDisconnectedEventArgs : EventArgs { - public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientAuthenticateResult authenticateResult, MqttClientDisconnectReason reason) + public MqttClientDisconnectedEventArgs(bool clientWasConnected, Exception exception, MqttClientConnectResult connectResult, MqttClientDisconnectReason reason) { ClientWasConnected = clientWasConnected; Exception = exception; - AuthenticateResult = authenticateResult; + ConnectResult = connectResult; Reason = reason; ReasonCode = reason; @@ -23,7 +23,7 @@ namespace MQTTnet.Client.Disconnecting /// Gets the authentication result. /// Hint: MQTT 5 feature only. /// - public MqttClientAuthenticateResult AuthenticateResult { get; } + public MqttClientConnectResult ConnectResult { get; } /// /// Gets or sets the reason. diff --git a/Source/MQTTnet/Client/IMqttClient.cs b/Source/MQTTnet/Client/IMqttClient.cs index 394ec8dc..03bee88c 100644 --- a/Source/MQTTnet/Client/IMqttClient.cs +++ b/Source/MQTTnet/Client/IMqttClient.cs @@ -28,7 +28,7 @@ namespace MQTTnet.Client /// IMqttClientDisconnectedHandler DisconnectedHandler { get; set; } - Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken); + Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken); Task DisconnectAsync(MqttClientDisconnectOptions options, CancellationToken cancellationToken); diff --git a/Source/MQTTnet/Client/MqttClient.cs b/Source/MQTTnet/Client/MqttClient.cs index 2e8bf91f..067f54f0 100644 --- a/Source/MQTTnet/Client/MqttClient.cs +++ b/Source/MQTTnet/Client/MqttClient.cs @@ -62,7 +62,7 @@ namespace MQTTnet.Client public IMqttClientOptions Options { get; private set; } - public async Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) + public async Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ChannelOptions == null) throw new ArgumentException("ChannelOptions are not set."); @@ -76,7 +76,7 @@ namespace MQTTnet.Client throw new InvalidOperationException("Not allowed to connect while connect/disconnect is pending."); } - MqttClientAuthenticateResult authenticateResult = null; + MqttClientConnectResult connectResult = null; try { @@ -102,7 +102,7 @@ namespace MQTTnet.Client _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken); - authenticateResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false); + connectResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false); } _lastPacketSentTimestamp = DateTime.UtcNow; @@ -119,10 +119,10 @@ namespace MQTTnet.Client var connectedHandler = ConnectedHandler; if (connectedHandler != null) { - await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(authenticateResult)).ConfigureAwait(false); + await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(connectResult)).ConfigureAwait(false); } - return authenticateResult; + return connectResult; } catch (Exception exception) { @@ -130,7 +130,7 @@ namespace MQTTnet.Client _logger.Error(exception, "Error while connecting with server."); - await DisconnectInternalAsync(null, exception, authenticateResult).ConfigureAwait(false); + await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false); throw; } @@ -283,9 +283,9 @@ namespace MQTTnet.Client base.Dispose(disposing); } - async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) + async Task AuthenticateAsync(IMqttChannelAdapter channelAdapter, MqttApplicationMessage willApplicationMessage, CancellationToken cancellationToken) { - MqttClientAuthenticateResult result; + MqttClientConnectResult result; try { @@ -324,19 +324,19 @@ namespace MQTTnet.Client if (IsConnected) throw new MqttProtocolViolationException(message); } - Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult) + Task DisconnectInternalAsync(Task sender, Exception exception, MqttClientConnectResult connectResult) { var clientWasConnected = IsConnected; if (!DisconnectIsPendingOrFinished()) { - return DisconnectCoreAsync(sender, exception, authenticateResult, clientWasConnected); + return DisconnectCoreAsync(sender, exception, connectResult, clientWasConnected); } return PlatformAbstractionLayer.CompletedTask; } - async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientAuthenticateResult authenticateResult, bool clientWasConnected) + async Task DisconnectCoreAsync(Task sender, Exception exception, MqttClientConnectResult connectResult, bool clientWasConnected) { TryInitiateDisconnect(); @@ -380,7 +380,7 @@ namespace MQTTnet.Client { // This handler must be executed in a new thread because otherwise a dead lock may happen // when trying to reconnect in that handler etc. - Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, authenticateResult, _disconnectReason))).RunInBackground(_logger); + Task.Run(() => disconnectedHandler.HandleDisconnectedAsync(new MqttClientDisconnectedEventArgs(clientWasConnected, exception, connectResult, _disconnectReason))).RunInBackground(_logger); } } } @@ -419,7 +419,7 @@ namespace MQTTnet.Client packetIdentifier = packetWithIdentifier.PacketIdentifier; } - using (var packetAwaiter = _packetDispatcher.AddAwaiter(packetIdentifier)) + using (var packetAwaiter = _packetDispatcher.AddAwaitable(packetIdentifier)) { try { diff --git a/Source/MQTTnet/Client/MqttClientExtensions.cs b/Source/MQTTnet/Client/MqttClientExtensions.cs index 91a7c75e..117f3984 100644 --- a/Source/MQTTnet/Client/MqttClientExtensions.cs +++ b/Source/MQTTnet/Client/MqttClientExtensions.cs @@ -112,7 +112,7 @@ namespace MQTTnet.Client return client; } - public static Task ReconnectAsync(this IMqttClient client) + public static Task ReconnectAsync(this IMqttClient client) { if (client == null) throw new ArgumentNullException(nameof(client)); @@ -124,7 +124,7 @@ namespace MQTTnet.Client return client.ConnectAsync(client.Options); } - public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken) + public static Task ReconnectAsync(this IMqttClient client, CancellationToken cancellationToken) { if (client == null) throw new ArgumentNullException(nameof(client)); @@ -195,7 +195,7 @@ namespace MQTTnet.Client return client.UnsubscribeAsync(options); } - public static Task ConnectAsync(this IMqttClient client, IMqttClientOptions options) + public static Task ConnectAsync(this IMqttClient client, IMqttClientOptions options) { if (client == null) throw new ArgumentNullException(nameof(client)); diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs index 2c43c42a..2e229987 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptions.cs @@ -18,7 +18,7 @@ namespace MQTTnet.Client.Subscribing /// The broker will return the subscription identifier associated with this PUBLISH packet and the PUBLISH packet to the client when need to forward PUBLISH packets matching this subscription to this client. /// Hint: MQTT 5 feature only. /// - public uint? SubscriptionIdentifier { get; set; } + public uint SubscriptionIdentifier { get; set; } /// /// Gets or sets the user properties. diff --git a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs index 14d2ad6e..96a66433 100644 --- a/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Subscribing/MqttClientSubscribeOptionsBuilder.cs @@ -2,12 +2,13 @@ using MQTTnet.Protocol; using System; using System.Collections.Generic; +using MQTTnet.Exceptions; namespace MQTTnet.Client.Subscribing { - public class MqttClientSubscribeOptionsBuilder + public sealed class MqttClientSubscribeOptionsBuilder { - private readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions(); + readonly MqttClientSubscribeOptions _subscribeOptions = new MqttClientSubscribeOptions(); /// /// Adds the user property to the subscribe options. @@ -31,8 +32,13 @@ namespace MQTTnet.Client.Subscribing return this; } - public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint? subscriptionIdentifier) + public MqttClientSubscribeOptionsBuilder WithSubscriptionIdentifier(uint subscriptionIdentifier) { + if (subscriptionIdentifier == 0) + { + throw new MqttProtocolViolationException("Subscription identifier cannot be 0."); + } + _subscribeOptions.SubscriptionIdentifier = subscriptionIdentifier; return this; } @@ -40,9 +46,9 @@ namespace MQTTnet.Client.Subscribing public MqttClientSubscribeOptionsBuilder WithTopicFilter( string topic, MqttQualityOfServiceLevel qualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce, - bool? noLocal = null, - bool? retainAsPublished = null, - MqttRetainHandling? retainHandling = null) + bool noLocal = false, + bool retainAsPublished = false, + MqttRetainHandling retainHandling = MqttRetainHandling.SendAtSubscribe) { return WithTopicFilter(new MqttTopicFilter { diff --git a/Source/MQTTnet/Diagnostics/MqttNetScopedLoggerExtensions.cs b/Source/MQTTnet/Diagnostics/MqttNetScopedLoggerExtensions.cs index 8ecc94c1..961e5cd1 100644 --- a/Source/MQTTnet/Diagnostics/MqttNetScopedLoggerExtensions.cs +++ b/Source/MQTTnet/Diagnostics/MqttNetScopedLoggerExtensions.cs @@ -8,25 +8,55 @@ namespace MQTTnet.Diagnostics { logger.Publish(MqttNetLogLevel.Verbose, message, parameters, null); } + + public static void Verbose(this IMqttNetScopedLogger logger, string message) + { + logger.Publish(MqttNetLogLevel.Verbose, message, null, null); + } public static void Info(this IMqttNetScopedLogger logger, string message, params object[] parameters) { logger.Publish(MqttNetLogLevel.Info, message, parameters, null); } + + public static void Info(this IMqttNetScopedLogger logger, string message) + { + logger.Publish(MqttNetLogLevel.Info, message, null, null); + } public static void Warning(this IMqttNetScopedLogger logger, Exception exception, string message, params object[] parameters) { logger.Publish(MqttNetLogLevel.Warning, message, parameters, exception); } + public static void Warning(this IMqttNetScopedLogger logger, Exception exception, string message) + { + logger.Publish(MqttNetLogLevel.Warning, message, null, exception); + } + public static void Warning(this IMqttNetScopedLogger logger, string message, params object[] parameters) { logger.Publish(MqttNetLogLevel.Warning, message, parameters, null); } + + public static void Warning(this IMqttNetScopedLogger logger, string message) + { + logger.Publish(MqttNetLogLevel.Warning, message, null, null); + } public static void Error(this IMqttNetScopedLogger logger, Exception exception, string message, params object[] parameters) { logger.Publish(MqttNetLogLevel.Error, message, parameters, exception); } + + public static void Error(this IMqttNetScopedLogger logger, Exception exception, string message) + { + logger.Publish(MqttNetLogLevel.Error, message, null, exception); + } + + public static void Error(this IMqttNetScopedLogger logger, string message) + { + logger.Publish(MqttNetLogLevel.Error, message, null, null); + } } } diff --git a/Source/MQTTnet/Formatter/IMqttDataConverter.cs b/Source/MQTTnet/Formatter/IMqttDataConverter.cs index 71fc30be..7f23f628 100644 --- a/Source/MQTTnet/Formatter/IMqttDataConverter.cs +++ b/Source/MQTTnet/Formatter/IMqttDataConverter.cs @@ -8,6 +8,7 @@ using MQTTnet.Client.Unsubscribing; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; +using MQTTnet.Server.Internal; namespace MQTTnet.Formatter { @@ -15,13 +16,13 @@ namespace MQTTnet.Formatter { MqttApplicationMessage CreateApplicationMessage(MqttPublishPacket publishPacket); - MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket); + MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket); MqttClientPublishResult CreateClientPublishResult(MqttPubAckPacket pubAckPacket); MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket); - Client.Subscribing.MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket); + MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket); MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket); @@ -41,7 +42,7 @@ namespace MQTTnet.Formatter MqttSubscribePacket CreateSubscribePacket(MqttClientSubscribeOptions options); - MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult); + MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult); MqttUnsubscribePacket CreateUnsubscribePacket(MqttClientUnsubscribeOptions options); diff --git a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs index 9fc4d2a1..775dc00c 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310DataConverter.cs @@ -11,7 +11,7 @@ using MQTTnet.Server; using System; using System.Collections.Generic; using System.Linq; -using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult; +using MQTTnet.Server.Internal; namespace MQTTnet.Formatter.V3 { @@ -85,12 +85,12 @@ namespace MQTTnet.Formatter.V3 }; } - public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) { if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); MqttClientConnectResultCode resultCode; - switch (connAckPacket.ReturnCode.Value) + switch (connAckPacket.ReturnCode) { case MqttConnectReturnCode.ConnectionAccepted: { @@ -132,8 +132,10 @@ namespace MQTTnet.Formatter.V3 throw new MqttProtocolViolationException("Received unexpected return code."); } - return new MqttClientAuthenticateResult + return new MqttClientConnectResult { + RetainAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. + WildcardSubscriptionAvailable = true, // Always true because v3.1.1 does not have a way to "disable" that feature. IsSessionPresent = connAckPacket.IsSessionPresent, ResultCode = resultCode }; @@ -205,7 +207,7 @@ namespace MQTTnet.Formatter.V3 return subscribePacket; } - public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult) + public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult)); diff --git a/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs index d872b1cf..16fb380e 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV310PacketFormatter.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Net; using System.Runtime.CompilerServices; using MQTTnet.Adapter; using MQTTnet.Exceptions; @@ -407,7 +408,7 @@ namespace MQTTnet.Formatter.V3 protected virtual byte EncodeConnAckPacket(MqttConnAckPacket packet, IMqttPacketWriter packetWriter) { packetWriter.Write(0); // Reserved. - packetWriter.Write((byte)packet.ReturnCode.Value); + packetWriter.Write((byte)packet.ReturnCode); return MqttPacketWriter.BuildFixedHeader(MqttControlPacketType.ConnAck); } @@ -520,6 +521,21 @@ namespace MQTTnet.Formatter.V3 { foreach (var topicFilter in packet.TopicFilters) { + if (topicFilter.NoLocal) + { + throw new MqttProtocolViolationException("NoLocal is not supported in 3.1.1."); + } + + if (topicFilter.RetainAsPublished) + { + throw new MqttProtocolViolationException("RetainAsPublished is not supported in 3.1.1."); + } + + if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe) + { + throw new MqttProtocolViolationException("RetainHandling is not supported in 3.1.1."); + } + packetWriter.WriteWithLengthPrefix(topicFilter.Topic); packetWriter.Write((byte)topicFilter.QualityOfServiceLevel); } diff --git a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs index 49da6fcd..5f034c0b 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500DataConverter.cs @@ -11,7 +11,7 @@ using MQTTnet.Server; using System; using System.Collections.Generic; using System.Linq; -using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult; +using MQTTnet.Server.Internal; namespace MQTTnet.Formatter.V5 { @@ -115,16 +115,16 @@ namespace MQTTnet.Formatter.V5 }; } - public MqttClientAuthenticateResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) + public MqttClientConnectResult CreateClientConnectResult(MqttConnAckPacket connAckPacket) { if (connAckPacket == null) throw new ArgumentNullException(nameof(connAckPacket)); - return new MqttClientAuthenticateResult + return new MqttClientConnectResult { IsSessionPresent = connAckPacket.IsSessionPresent, - ResultCode = (MqttClientConnectResultCode)(int)(connAckPacket.ReasonCode ?? 0), - WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable, - RetainAvailable = connAckPacket.Properties?.RetainAvailable, + ResultCode = (MqttClientConnectResultCode)(int)connAckPacket.ReasonCode, + WildcardSubscriptionAvailable = connAckPacket.Properties?.WildcardSubscriptionAvailable ?? true, + RetainAvailable = connAckPacket.Properties?.RetainAvailable ?? true, AssignedClientIdentifier = connAckPacket.Properties?.AssignedClientIdentifier, AuthenticationMethod = connAckPacket.Properties?.AuthenticationMethod, AuthenticationData = connAckPacket.Properties?.AuthenticationData, @@ -133,13 +133,13 @@ namespace MQTTnet.Formatter.V5 ReceiveMaximum = connAckPacket.Properties?.ReceiveMaximum, MaximumQoS = connAckPacket.Properties?.MaximumQoS ?? MqttQualityOfServiceLevel.ExactlyOnce, ResponseInformation = connAckPacket.Properties?.ResponseInformation, - TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum, + TopicAliasMaximum = connAckPacket.Properties?.TopicAliasMaximum ?? 0, ServerReference = connAckPacket.Properties?.ServerReference, ServerKeepAlive = connAckPacket.Properties?.ServerKeepAlive, SessionExpiryInterval = connAckPacket.Properties?.SessionExpiryInterval, - SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable, - SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable, - UserProperties = connAckPacket.Properties?.UserProperties + SubscriptionIdentifiersAvailable = connAckPacket.Properties?.SubscriptionIdentifiersAvailable ?? true, + SharedSubscriptionAvailable = connAckPacket.Properties?.SharedSubscriptionAvailable ?? true, + UserProperties = connAckPacket.Properties?.UserProperties ?? new List() }; } @@ -180,12 +180,18 @@ namespace MQTTnet.Formatter.V5 ReasonCode = connectionValidatorContext.ReasonCode, Properties = new MqttConnAckPacketProperties { + RetainAvailable = true, + SubscriptionIdentifiersAvailable = true, + SharedSubscriptionAvailable = false, + TopicAliasMaximum = ushort.MaxValue, + WildcardSubscriptionAvailable = true, + UserProperties = connectionValidatorContext.ResponseUserProperties, AuthenticationMethod = connectionValidatorContext.AuthenticationMethod, AuthenticationData = connectionValidatorContext.ResponseAuthenticationData, AssignedClientIdentifier = connectionValidatorContext.AssignedClientIdentifier, ReasonString = connectionValidatorContext.ReasonString, - TopicAliasMaximum = ushort.MaxValue + ServerReference = connectionValidatorContext.ServerReference } }; } @@ -242,7 +248,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, Server.MqttClientSubscribeResult subscribeResult) + public MqttSubAckPacket CreateSubAckPacket(MqttSubscribePacket subscribePacket, SubscribeResult subscribeResult) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); if (subscribeResult == null) throw new ArgumentNullException(nameof(subscribeResult)); diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs index fe355060..6fc6365e 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketDecoder.cs @@ -9,9 +9,9 @@ namespace MQTTnet.Formatter.V5 { public class MqttV500PacketDecoder { - private static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket(); + static readonly MqttPingReqPacket PingReqPacket = new MqttPingReqPacket(); - private static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket(); + static readonly MqttPingRespPacket PingRespPacket = new MqttPingRespPacket(); public MqttBasePacket Decode(ReceivedMqttPacket receivedMqttPacket) { @@ -45,7 +45,7 @@ namespace MQTTnet.Formatter.V5 } } - private static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeConnectPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -214,7 +214,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeConnAckPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -225,15 +225,17 @@ namespace MQTTnet.Formatter.V5 IsSessionPresent = (acknowledgeFlags & 0x1) > 0, ReasonCode = (MqttConnectReasonCode)body.ReadByte() }; + + // Set all default values according to specification. When they are missing the often + // indicate that a feature is available. + packet.Properties.RetainAvailable = true; + packet.Properties.SharedSubscriptionAvailable = true; + packet.Properties.SubscriptionIdentifiersAvailable = true; + packet.Properties.WildcardSubscriptionAvailable = true; var propertiesReader = new MqttV500PropertiesReader(body); while (propertiesReader.MoveNext()) { - if (packet.Properties == null) - { - packet.Properties = new MqttConnAckPacketProperties(); - } - if (propertiesReader.CurrentPropertyId == MqttPropertyId.SessionExpiryInterval) { packet.Properties.SessionExpiryInterval = propertiesReader.ReadSessionExpiryInterval(); @@ -316,7 +318,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeDisconnectPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -363,7 +365,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeSubscribePacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -422,7 +424,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeSubAckPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -467,7 +469,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeUnsubscribePacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -507,7 +509,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeUnsubAckPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -552,17 +554,17 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodePingReqPacket() + static MqttBasePacket DecodePingReqPacket() { return PingReqPacket; } - private static MqttBasePacket DecodePingRespPacket() + static MqttBasePacket DecodePingRespPacket() { return PingRespPacket; } - private static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body) + static MqttBasePacket DecodePublishPacket(byte header, IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -647,7 +649,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodePubAckPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -694,7 +696,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodePubRecPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -741,7 +743,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodePubRelPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -788,7 +790,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodePubCompPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -835,7 +837,7 @@ namespace MQTTnet.Formatter.V5 return packet; } - private static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body) + static MqttBasePacket DecodeAuthPacket(IMqttPacketBodyReader body) { ThrowIfBodyIsEmpty(body); @@ -888,7 +890,7 @@ namespace MQTTnet.Formatter.V5 } // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - private static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body) + static void ThrowIfBodyIsEmpty(IMqttPacketBodyReader body) { if (body == null || body.Length == 0) { diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs index 3f7fe081..abfc5271 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500PacketEncoder.cs @@ -173,12 +173,7 @@ namespace MQTTnet.Formatter.V5 { if (packet == null) throw new ArgumentNullException(nameof(packet)); if (packetWriter == null) throw new ArgumentNullException(nameof(packetWriter)); - - if (!packet.ReasonCode.HasValue) - { - ThrowReasonCodeNotSetException(); - } - + byte connectAcknowledgeFlags = 0x0; if (packet.IsSessionPresent) { @@ -186,7 +181,7 @@ namespace MQTTnet.Formatter.V5 } packetWriter.Write(connectAcknowledgeFlags); - packetWriter.Write((byte)packet.ReasonCode.Value); + packetWriter.Write((byte)packet.ReasonCode); var propertiesWriter = new MqttV500PropertiesWriter(); if (packet.Properties != null) @@ -247,19 +242,18 @@ namespace MQTTnet.Formatter.V5 var propertiesWriter = new MqttV500PropertiesWriter(); if (packet.Properties != null) { - if (packet.Properties.TopicAlias == 0) - { - throw new MqttProtocolViolationException("A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic Alias which has the value 0 [MQTT-3.3.2-8]."); - } - propertiesWriter.WriteContentType(packet.Properties.ContentType); propertiesWriter.WriteCorrelationData(packet.Properties.CorrelationData); propertiesWriter.WriteMessageExpiryInterval(packet.Properties.MessageExpiryInterval); propertiesWriter.WritePayloadFormatIndicator(packet.Properties.PayloadFormatIndicator); propertiesWriter.WriteResponseTopic(packet.Properties.ResponseTopic); propertiesWriter.WriteSubscriptionIdentifiers(packet.Properties.SubscriptionIdentifiers); - propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias); propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); + + if (packet.Properties.TopicAlias > 0) + { + propertiesWriter.WriteTopicAlias(packet.Properties.TopicAlias); + } } propertiesWriter.WriteTo(packetWriter); @@ -411,7 +405,11 @@ namespace MQTTnet.Formatter.V5 var propertiesWriter = new MqttV500PropertiesWriter(); if (packet.Properties != null) { - propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier); + if (packet.Properties.SubscriptionIdentifier > 0) + { + propertiesWriter.WriteSubscriptionIdentifier(packet.Properties.SubscriptionIdentifier); + } + propertiesWriter.WriteUserProperties(packet.Properties.UserProperties); } @@ -425,19 +423,19 @@ namespace MQTTnet.Formatter.V5 var options = (byte)topicFilter.QualityOfServiceLevel; - if (topicFilter.NoLocal == true) + if (topicFilter.NoLocal) { options |= 1 << 2; } - if (topicFilter.RetainAsPublished == true) + if (topicFilter.RetainAsPublished) { options |= 1 << 3; } - if (topicFilter.RetainHandling.HasValue) + if (topicFilter.RetainHandling != MqttRetainHandling.SendAtSubscribe) { - options |= (byte)((byte)topicFilter.RetainHandling.Value << 4); + options |= (byte)((byte)topicFilter.RetainHandling << 4); } packetWriter.Write(options); diff --git a/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs index 6dd028cd..d9c1b91d 100644 --- a/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs +++ b/Source/MQTTnet/Formatter/V5/MqttV500PropertiesWriter.cs @@ -74,12 +74,12 @@ namespace MQTTnet.Formatter.V5 WriteAsFourByteInteger(MqttPropertyId.SessionExpiryInterval, value); } - public void WriteSubscriptionIdentifier(uint? value) + public void WriteSubscriptionIdentifier(uint value) { WriteAsVariableLengthInteger(MqttPropertyId.SubscriptionIdentifier, value); } - public void WriteSubscriptionIdentifiers(IEnumerable value) + public void WriteSubscriptionIdentifiers(ICollection value) { if (value == null) { @@ -148,9 +148,15 @@ namespace MQTTnet.Formatter.V5 WriteAsFourByteInteger(MqttPropertyId.MaximumPacketSize, value); } - public void WriteRetainAvailable(bool? value) + public void WriteRetainAvailable(bool value) { - Write(MqttPropertyId.RetainAvailable, value); + if (value) + { + // Absence of the flag means it is supported! + return; + } + + Write(MqttPropertyId.RetainAvailable, false); } public void WriteAssignedClientIdentifier(string value) @@ -168,14 +174,26 @@ namespace MQTTnet.Formatter.V5 Write(MqttPropertyId.WildcardSubscriptionAvailable, value); } - public void WriteSubscriptionIdentifiersAvailable(bool? value) + public void WriteSubscriptionIdentifiersAvailable(bool value) { - Write(MqttPropertyId.SubscriptionIdentifiersAvailable, value); + if (value) + { + // Absence of the flag means it is supported! + return; + } + + Write(MqttPropertyId.SubscriptionIdentifiersAvailable, false); } - public void WriteSharedSubscriptionAvailable(bool? value) + public void WriteSharedSubscriptionAvailable(bool value) { - Write(MqttPropertyId.SharedSubscriptionAvailable, value); + if (value) + { + // Absence of the flag means it is supported! + return; + } + + Write(MqttPropertyId.SharedSubscriptionAvailable, false); } public void WriteServerKeepAlive(ushort? value) @@ -221,15 +239,10 @@ namespace MQTTnet.Formatter.V5 _packetWriter.Write(value.Value); } - void WriteAsVariableLengthInteger(MqttPropertyId id, uint? value) + void WriteAsVariableLengthInteger(MqttPropertyId id, uint value) { - if (!value.HasValue) - { - return; - } - _packetWriter.Write((byte)id); - _packetWriter.WriteVariableLengthInteger(value.Value); + _packetWriter.WriteVariableLengthInteger(value); } void WriteAsFourByteInteger(MqttPropertyId id, uint? value) diff --git a/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs index 49b9bfb2..12f106d7 100644 --- a/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs +++ b/Source/MQTTnet/Implementations/PlatformAbstractionLayer.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace MQTTnet.Implementations @@ -18,7 +19,16 @@ namespace MQTTnet.Implementations public static void Sleep(TimeSpan timeout) { #if !NETSTANDARD1_3 && !WINDOWS_UWP - System.Threading.Thread.Sleep(timeout); + 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 diff --git a/Source/MQTTnet/MqttFactory.cs b/Source/MQTTnet/MqttFactory.cs index e49488ac..cde9b0fd 100644 --- a/Source/MQTTnet/MqttFactory.cs +++ b/Source/MQTTnet/MqttFactory.cs @@ -7,6 +7,9 @@ using MQTTnet.Server; using System; using System.Collections.Generic; using System.Linq; +using MQTTnet.Client.Options; +using MQTTnet.Client.Subscribing; +using MQTTnet.Client.Unsubscribing; namespace MQTTnet { @@ -120,5 +123,35 @@ namespace MQTTnet return new MqttServer(serverAdapters, DefaultLogger); } + + public MqttClientOptionsBuilder CreateClientOptionsBuilder() + { + return new MqttClientOptionsBuilder(); + } + + public MqttServerOptionsBuilder CreateServerOptionsBuilder() + { + return new MqttServerOptionsBuilder(); + } + + public MqttClientSubscribeOptionsBuilder CreateSubscribeOptionsBuilder() + { + return new MqttClientSubscribeOptionsBuilder(); + } + + public MqttClientUnsubscribeOptionsBuilder CreateUnsubscribeOptionsBuilder() + { + return new MqttClientUnsubscribeOptionsBuilder(); + } + + public MqttTopicFilterBuilder CreateTopicFilterBuilder() + { + return new MqttTopicFilterBuilder(); + } + + public MqttApplicationMessageBuilder CreateApplicationMessageBuilder() + { + return new MqttApplicationMessageBuilder(); + } } } \ No newline at end of file diff --git a/Source/MQTTnet/MqttTopicFilter.cs b/Source/MQTTnet/MqttTopicFilter.cs index e9cea55a..214aa322 100644 --- a/Source/MQTTnet/MqttTopicFilter.cs +++ b/Source/MQTTnet/MqttTopicFilter.cs @@ -29,22 +29,22 @@ namespace MQTTnet public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } /// - /// Gets or sets a value indicating whether to not send messages originating on this client (noLocal) or not. + /// Gets or sets a value indicating whether the sender will not receive its own published application messages. /// /// Hint: MQTT 5 feature only. - public bool? NoLocal { get; set; } + public bool NoLocal { get; set; } /// /// Gets or sets a value indicating whether messages are retained as published or not. /// Hint: MQTT 5 feature only. /// - public bool? RetainAsPublished { get; set; } + public bool RetainAsPublished { get; set; } /// /// Gets or sets the retain handling. /// Hint: MQTT 5 feature only. /// - public MqttRetainHandling? RetainHandling { get; set; } + public MqttRetainHandling RetainHandling { get; set; } public override string ToString() { diff --git a/Source/MQTTnet/MqttTopicFilterBuilder.cs b/Source/MQTTnet/MqttTopicFilterBuilder.cs index 0143c411..055f655f 100644 --- a/Source/MQTTnet/MqttTopicFilterBuilder.cs +++ b/Source/MQTTnet/MqttTopicFilterBuilder.cs @@ -27,9 +27,9 @@ namespace MQTTnet /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). /// string _topic; - bool? _noLocal; - bool? _retainAsPublished; - MqttRetainHandling? _retainHandling; + bool _noLocal; + bool _retainAsPublished; + MqttRetainHandling _retainHandling = MqttRetainHandling.SendAtSubscribe; public MqttTopicFilterBuilder WithTopic(string topic) { @@ -61,19 +61,40 @@ namespace MQTTnet return this; } + [Obsolete("Please use overload with _bool_ instead.")] public MqttTopicFilterBuilder WithNoLocal(bool? value = true) + { + _noLocal = value == true; + return this; + } + + public MqttTopicFilterBuilder WithNoLocal(bool value = true) { _noLocal = value; return this; } + [Obsolete("Please use overload with _bool_ instead.")] public MqttTopicFilterBuilder WithRetainAsPublished(bool? value = true) + { + _retainAsPublished = value == true; + return this; + } + + public MqttTopicFilterBuilder WithRetainAsPublished(bool value = true) { _retainAsPublished = value; return this; } + [Obsolete("Please use overload with _MqttRetainHandling_ instead.")] public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling? value) + { + _retainHandling = value ?? MqttRetainHandling.SendAtSubscribe; + return this; + } + + public MqttTopicFilterBuilder WithRetainHandling(MqttRetainHandling value) { _retainHandling = value; return this; diff --git a/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs similarity index 64% rename from Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs rename to Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs index 8324d4ca..06e86608 100644 --- a/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/IMqttPacketAwaitable.cs @@ -3,9 +3,9 @@ using System; namespace MQTTnet.PacketDispatcher { - public interface IMqttPacketAwaiter : IDisposable + public interface IMqttPacketAwaitable : IDisposable { - MqttPacketAwaiterPacketFilter PacketFilter { get; } + MqttPacketAwaitableFilter Filter { get; } void Complete(MqttBasePacket packet); diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs similarity index 89% rename from Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs rename to Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs index 9ed6e71c..de0df74d 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitable.cs @@ -6,14 +6,14 @@ using System.Threading.Tasks; namespace MQTTnet.PacketDispatcher { - public sealed class MqttPacketAwaiter : IMqttPacketAwaiter where TPacket : MqttBasePacket + public sealed class MqttPacketAwaitable : IMqttPacketAwaitable where TPacket : MqttBasePacket { readonly TaskCompletionSource _taskCompletionSource; readonly MqttPacketDispatcher _owningPacketDispatcher; - public MqttPacketAwaiter(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher) + public MqttPacketAwaitable(ushort packetIdentifier, MqttPacketDispatcher owningPacketDispatcher) { - PacketFilter = new MqttPacketAwaiterPacketFilter + Filter = new MqttPacketAwaitableFilter { Type = typeof(TPacket), Identifier = packetIdentifier @@ -27,7 +27,7 @@ namespace MQTTnet.PacketDispatcher #endif } - public MqttPacketAwaiterPacketFilter PacketFilter { get; } + public MqttPacketAwaitableFilter Filter { get; } public async Task WaitOneAsync(TimeSpan timeout) { @@ -88,7 +88,7 @@ namespace MQTTnet.PacketDispatcher public void Dispose() { - _owningPacketDispatcher.RemoveAwaiter(this); + _owningPacketDispatcher.RemoveAwaitable(this); } } } \ No newline at end of file diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiterPacketFilter.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs similarity index 74% rename from Source/MQTTnet/PacketDispatcher/MqttPacketAwaiterPacketFilter.cs rename to Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs index a36acc5a..c1110dbb 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketAwaiterPacketFilter.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketAwaitableFilter.cs @@ -2,7 +2,7 @@ namespace MQTTnet.PacketDispatcher { - public sealed class MqttPacketAwaiterPacketFilter + public sealed class MqttPacketAwaitableFilter { public Type Type { get; set; } diff --git a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs index aafabf23..2d537c66 100644 --- a/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs +++ b/Source/MQTTnet/PacketDispatcher/MqttPacketDispatcher.cs @@ -6,33 +6,33 @@ namespace MQTTnet.PacketDispatcher { public sealed class MqttPacketDispatcher { - readonly List _awaiters = new List(); + readonly List _awaitables = new List(); public void FailAll(Exception exception) { if (exception == null) throw new ArgumentNullException(nameof(exception)); - lock (_awaiters) + lock (_awaitables) { - foreach (var awaiter in _awaiters) + foreach (var awaitable in _awaitables) { - awaiter.Fail(exception); + awaitable.Fail(exception); } - _awaiters.Clear(); + _awaitables.Clear(); } } public void CancelAll() { - lock (_awaiters) + lock (_awaitables) { - foreach (var entry in _awaiters) + foreach (var awaitable in _awaitables) { - entry.Cancel(); + awaitable.Cancel(); } - _awaiters.Clear(); + _awaitables.Clear(); } } @@ -47,54 +47,54 @@ namespace MQTTnet.PacketDispatcher } var packetType = packet.GetType(); - var matchingAwaiters = new List(); + var awaitables = new List(); - lock (_awaiters) + lock (_awaitables) { - for (var i = _awaiters.Count - 1; i >= 0; i--) + for (var i = _awaitables.Count - 1; i >= 0; i--) { - var entry = _awaiters[i]; + var entry = _awaitables[i]; // Note: The PingRespPacket will also arrive here and has NO identifier but there // is code which waits for it. So the code must be able to deal with filters which // are referring to the type only (identifier is 0)! - if (entry.PacketFilter.Type != packetType || entry.PacketFilter.Identifier != identifier) + if (entry.Filter.Type != packetType || entry.Filter.Identifier != identifier) { continue; } - matchingAwaiters.Add(entry); - _awaiters.RemoveAt(i); + awaitables.Add(entry); + _awaitables.RemoveAt(i); } } - foreach (var matchingEntry in matchingAwaiters) + foreach (var matchingEntry in awaitables) { matchingEntry.Complete(packet); } - return matchingAwaiters.Count > 0; + return awaitables.Count > 0; } - public MqttPacketAwaiter AddAwaiter(ushort packetIdentifier) where TResponsePacket : MqttBasePacket + public MqttPacketAwaitable AddAwaitable(ushort packetIdentifier) where TResponsePacket : MqttBasePacket { - var awaiter = new MqttPacketAwaiter(packetIdentifier, this); + var awaitable = new MqttPacketAwaitable(packetIdentifier, this); - lock (_awaiters) + lock (_awaitables) { - _awaiters.Add(awaiter); + _awaitables.Add(awaitable); } - return awaiter; + return awaitable; } - public void RemoveAwaiter(IMqttPacketAwaiter awaiter) + public void RemoveAwaitable(IMqttPacketAwaitable awaitable) { - if (awaiter == null) throw new ArgumentNullException(nameof(awaiter)); + if (awaitable == null) throw new ArgumentNullException(nameof(awaitable)); - lock (_awaiters) + lock (_awaitables) { - _awaiters.Remove(awaiter); + _awaitables.Remove(awaitable); } } } diff --git a/Source/MQTTnet/Packets/MqttConnAckPacket.cs b/Source/MQTTnet/Packets/MqttConnAckPacket.cs index 6eceab7c..b8db02d2 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacket.cs @@ -4,22 +4,23 @@ namespace MQTTnet.Packets { public sealed class MqttConnAckPacket : MqttBasePacket { - public MqttConnectReturnCode? ReturnCode { get; set; } - - #region Added in MQTTv3.1.1 - + public MqttConnectReturnCode ReturnCode { get; set; } + + /// + /// Added in MQTT 3.1.1. + /// public bool IsSessionPresent { get; set; } + + /// + /// Added in MQTT 5.0.0. + /// + public MqttConnectReasonCode ReasonCode { get; set; } - #endregion - - #region Added in MQTTv5.0.0 - - public MqttConnectReasonCode? ReasonCode { get; set; } - - public MqttConnAckPacketProperties Properties { get; set; } - - #endregion - + /// + /// Added in MQTT 5.0.0. + /// + public MqttConnAckPacketProperties Properties { get; set; } = new MqttConnAckPacketProperties(); + public override string ToString() { return string.Concat("ConnAck: [ReturnCode=", ReturnCode, "] [ReasonCode=", ReasonCode, "] [IsSessionPresent=", IsSessionPresent, "]"); diff --git a/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs index d9dba9a2..519512b9 100644 --- a/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttConnAckPacketProperties.cs @@ -11,23 +11,23 @@ namespace MQTTnet.Packets public MqttQualityOfServiceLevel? MaximumQoS { get; set; } - public bool? RetainAvailable { get; set; } + public bool RetainAvailable { get; set; } public uint? MaximumPacketSize { get; set; } public string AssignedClientIdentifier { get; set; } - public ushort? TopicAliasMaximum { get; set; } + public ushort TopicAliasMaximum { get; set; } public string ReasonString { get; set; } public List UserProperties { get; set; } - public bool? WildcardSubscriptionAvailable { get; set; } + public bool WildcardSubscriptionAvailable { get; set; } - public bool? SubscriptionIdentifiersAvailable { get; set; } + public bool SubscriptionIdentifiersAvailable { get; set; } - public bool? SharedSubscriptionAvailable { get; set; } + public bool SharedSubscriptionAvailable { get; set; } public ushort? ServerKeepAlive { get; set; } diff --git a/Source/MQTTnet/Packets/MqttSubscribePacket.cs b/Source/MQTTnet/Packets/MqttSubscribePacket.cs index d7f4da80..3743c9c2 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacket.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacket.cs @@ -9,12 +9,11 @@ namespace MQTTnet.Packets public List TopicFilters { get; set; } = new List(); - #region Added in MQTTv5 - - public MqttSubscribePacketProperties Properties { get; set; } - - #endregion - + /// + /// Added in MQTT V5. + /// + public MqttSubscribePacketProperties Properties { get; set; } = new MqttSubscribePacketProperties(); + public override string ToString() { var topicFiltersText = string.Join(",", TopicFilters.Select(f => f.Topic + "@" + f.QualityOfServiceLevel)); diff --git a/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs index 43831baf..8455f890 100644 --- a/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs +++ b/Source/MQTTnet/Packets/MqttSubscribePacketProperties.cs @@ -4,8 +4,11 @@ namespace MQTTnet.Packets { public sealed class MqttSubscribePacketProperties { - public uint? SubscriptionIdentifier { get; set; } + /// + /// It is a Protocol Error if the Subscription Identifier has a value of 0. + /// + public uint SubscriptionIdentifier { get; set; } - public List UserProperties { get; set; } + public List UserProperties { get; set; } = new List(); } } diff --git a/Source/MQTTnet/Server/GetSubscribedMessagesFilter.cs b/Source/MQTTnet/Server/GetSubscribedMessagesFilter.cs new file mode 100644 index 00000000..004efeaf --- /dev/null +++ b/Source/MQTTnet/Server/GetSubscribedMessagesFilter.cs @@ -0,0 +1,9 @@ +namespace MQTTnet.Server +{ + public sealed class GetSubscribedMessagesFilter + { + public bool IsNewSubscription { get; set; } + + public MqttTopicFilter TopicFilter { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs index 07d1c8a2..0acc78af 100644 --- a/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/IMqttRetainedMessagesManager.cs @@ -15,7 +15,5 @@ namespace MQTTnet.Server Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage); Task> GetMessagesAsync(); - - Task> GetSubscribedMessagesAsync(ICollection topicFilters); } } diff --git a/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs b/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs index 18a02fd3..b00b2083 100644 --- a/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs +++ b/Source/MQTTnet/Server/Internal/CheckSubscriptionsResult.cs @@ -1,4 +1,5 @@ -using MQTTnet.Protocol; +using System.Collections.Generic; +using MQTTnet.Protocol; namespace MQTTnet.Server.Internal { @@ -8,6 +9,10 @@ namespace MQTTnet.Server.Internal public bool IsSubscribed { get; set; } + public bool RetainAsPublished { get; set; } + + public List SubscriptionIdentifiers { get; set; } + /// /// Gets or sets the quality of service level. /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message. diff --git a/Source/MQTTnet/Server/Internal/MqttClientConnection.cs b/Source/MQTTnet/Server/Internal/MqttClientConnection.cs index 866624a0..4cb0c533 100644 --- a/Source/MQTTnet/Server/Internal/MqttClientConnection.cs +++ b/Source/MQTTnet/Server/Internal/MqttClientConnection.cs @@ -7,6 +7,7 @@ using MQTTnet.Client; using MQTTnet.Client.Disconnecting; using MQTTnet.Diagnostics; using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Implementations; using MQTTnet.Internal; using MQTTnet.PacketDispatcher; @@ -22,7 +23,6 @@ namespace MQTTnet.Server.Internal readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); - readonly IMqttRetainedMessagesManager _retainedMessagesManager; readonly MqttClientSessionsManager _sessionsManager; readonly IMqttChannelAdapter _channelAdapter; @@ -30,6 +30,7 @@ namespace MQTTnet.Server.Internal readonly IMqttServerOptions _serverOptions; readonly string _endpoint; + readonly MqttConnectPacket _connectPacket; CancellationTokenSource _cancellationToken; @@ -39,24 +40,22 @@ namespace MQTTnet.Server.Internal MqttClientSession session, IMqttServerOptions serverOptions, MqttClientSessionsManager sessionsManager, - IMqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger) { _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); - _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - + _channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); _endpoint = channelAdapter.Endpoint; Session = session ?? throw new ArgumentNullException(nameof(session)); - ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); + _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger.CreateScopedLogger(nameof(MqttClientConnection)); } - public string ClientId => ConnectPacket.ClientId; + public string ClientId => _connectPacket.ClientId; public string Endpoint => _endpoint; @@ -64,7 +63,7 @@ namespace MQTTnet.Server.Internal public bool IsRunning { get; private set; } - public MqttConnectPacket ConnectPacket { get; } + public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod; public bool IsReadingPacket => _channelAdapter.IsReadingPacket; @@ -116,7 +115,7 @@ namespace MQTTnet.Server.Internal { _logger.Info("Client '{0}': Session started.", ClientId); - Session.WillMessage = ConnectPacket.WillMessage; + Session.WillMessage = _connectPacket.WillMessage; using (var cancellationToken = new CancellationTokenSource()) { @@ -177,19 +176,19 @@ namespace MQTTnet.Server.Internal if (packet is MqttPublishPacket publishPacket) { - await HandleIncomingPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); + await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPubRelPacket pubRelPacket) { - await HandleIncomingPubRelPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); + await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttSubscribePacket subscribePacket) { - await HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken).ConfigureAwait(false); + await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttUnsubscribePacket unsubscribePacket) { - await HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false); + await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false); } else if (packet is MqttPingReqPacket) { @@ -239,33 +238,23 @@ namespace MQTTnet.Server.Internal { while (!cancellationToken.IsCancellationRequested) { - queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); - if (queuedApplicationMessage == null) - { - return; - } - + queuedApplicationMessage = await Session.ApplicationMessagesQueue.Dequeue(cancellationToken).ConfigureAwait(false); + + // Also check the cancellation token here because the dequeue is blocking and may take some time. if (cancellationToken.IsCancellationRequested) { return; } - - publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage); - publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel; - - // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. - publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage; - - publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false); - if (publishPacket == null) + + if (queuedApplicationMessage == null) { - // The interceptor has decided that the message is not relevant and will be fully ignored. continue; } - if (publishPacket.QualityOfServiceLevel > 0) + publishPacket = await CreatePublishPacket(queuedApplicationMessage).ConfigureAwait(false); + if (publishPacket == null) { - publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + continue; } if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) @@ -274,24 +263,24 @@ namespace MQTTnet.Server.Internal } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) { - using (var awaiter = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier)) + using (var awaitable = _packetDispatcher.AddAwaitable(publishPacket.PacketIdentifier)) { await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); - await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + await awaitable.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); } } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { - using (var awaiter1 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier)) - using (var awaiter2 = _packetDispatcher.AddAwaiter(publishPacket.PacketIdentifier)) + using (var awaitableRec = _packetDispatcher.AddAwaitable(publishPacket.PacketIdentifier)) + using (var awaitableComp = _packetDispatcher.AddAwaitable(publishPacket.PacketIdentifier)) { await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false); - var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + var pubRecPacket = await awaitableRec.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success); await SendPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false); - await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); + await awaitableComp.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false); } } @@ -329,29 +318,48 @@ namespace MQTTnet.Server.Internal } } + async Task CreatePublishPacket(MqttQueuedApplicationMessage queuedApplicationMessage) + { + var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage); + publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel; + + if (_channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500) + { + publishPacket.Properties.SubscriptionIdentifiers = queuedApplicationMessage.SubscriptionIdentifiers; + } + + // Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9]. + publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage; + + publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false); + if (publishPacket == null) + { + // The interceptor has decided that the message is not relevant and will be fully ignored. + return null; + } + + if (publishPacket.QualityOfServiceLevel > 0) + { + publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier(); + } + + return publishPacket; + } + void StopInternal() { _cancellationToken?.Cancel(); } - - async Task EnqueueSubscribedRetainedMessagesAsync(ICollection topicFilters) - { - var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); - foreach (var applicationMessage in retainedMessages) - { - Session.EnqueueApplicationMessage(applicationMessage, ClientId, true); - } - } - - Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) + + Task HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) { var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); return SendPacketAsync(pubCompPacket, cancellationToken); } - async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) + async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) { - var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false); + var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket).ConfigureAwait(false); var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult); await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false); @@ -361,19 +369,22 @@ namespace MQTTnet.Server.Internal StopInternal(); return; } - - await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false); + + foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages) + { + Session.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage); + } } - async Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) + async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken) { - var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); + var reasonCodes = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket).ConfigureAwait(false); var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes); await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false); } - Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) + Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken) { HandleTopicAlias(publishPacket); diff --git a/Source/MQTTnet/Server/Internal/MqttClientSession.cs b/Source/MQTTnet/Server/Internal/MqttClientSession.cs index 524f70b1..79bd558e 100644 --- a/Source/MQTTnet/Server/Internal/MqttClientSession.cs +++ b/Source/MQTTnet/Server/Internal/MqttClientSession.cs @@ -1,34 +1,25 @@ using System; using System.Collections.Generic; -using System.Threading.Tasks; -using MQTTnet.Diagnostics; using MQTTnet.Server.Status; namespace MQTTnet.Server.Internal { public sealed class MqttClientSession { - readonly IMqttNetScopedLogger _logger; - readonly DateTime _createdTimestamp = DateTime.UtcNow; - readonly IMqttRetainedMessagesManager _retainedMessagesManager; public MqttClientSession( string clientId, IDictionary items, MqttServerEventDispatcher eventDispatcher, - IMqttServerOptions serverOptions, - IMqttRetainedMessagesManager retainedMessagesManager, - IMqttNetLogger logger) + IMqttServerOptions serverOptions, + IMqttRetainedMessagesManager retainedMessagesManager) { ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId)); Items = items ?? throw new ArgumentNullException(nameof(items)); - _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); - SubscriptionsManager = new MqttClientSubscriptionsManager(this, eventDispatcher, serverOptions); - ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - _logger = logger.CreateScopedLogger(nameof(MqttClientSession)); + SubscriptionsManager = new MqttClientSubscriptionsManager(this, serverOptions, eventDispatcher, retainedMessagesManager); + ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions); } public string ClientId { get; } @@ -45,42 +36,7 @@ namespace MQTTnet.Server.Internal /// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// public IDictionary Items { get; } - - public bool EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage) - { - var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel); - if (!checkSubscriptionsResult.IsSubscribed) - { - return false; - } - - _logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", ClientId, applicationMessage.Topic); - - ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage); - - return true; - } - - public async Task SubscribeAsync(ICollection topicFilters) - { - if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters)); - - await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false); - - var matchingRetainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false); - foreach (var matchingRetainedMessage in matchingRetainedMessages) - { - EnqueueApplicationMessage(matchingRetainedMessage, null, true); - } - } - - public Task UnsubscribeAsync(IEnumerable topicFilters) - { - if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters)); - - return SubscriptionsManager.UnsubscribeAsync(topicFilters); - } - + public void FillSessionStatus(MqttSessionStatus status) { status.ClientId = ClientId; diff --git a/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs index e24bde03..da49c5b4 100644 --- a/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs +++ b/Source/MQTTnet/Server/Internal/MqttClientSessionApplicationMessagesQueue.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Internal; -using MQTTnet.Protocol; namespace MQTTnet.Server.Internal { @@ -18,20 +17,7 @@ namespace MQTTnet.Server.Internal } public int Count => _messageQueue.Count; - - public void Enqueue(MqttApplicationMessage applicationMessage, string senderClientId, MqttQualityOfServiceLevel qualityOfServiceLevel, bool isRetainedMessage) - { - if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage)); - - Enqueue(new MqttQueuedApplicationMessage - { - ApplicationMessage = applicationMessage, - SenderClientId = senderClientId, - SubscriptionQualityOfServiceLevel = qualityOfServiceLevel, - IsRetainedMessage = isRetainedMessage - }); - } - + public void Enqueue(MqttQueuedApplicationMessage queuedApplicationMessage) { if (queuedApplicationMessage == null) throw new ArgumentNullException(nameof(queuedApplicationMessage)); @@ -55,7 +41,7 @@ namespace MQTTnet.Server.Internal } } - public async Task DequeueAsync(CancellationToken cancellationToken) + public async Task Dequeue(CancellationToken cancellationToken) { var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false); if (!dequeueResult.IsSuccess) diff --git a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs index 4840969d..a6c3591a 100644 --- a/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs +++ b/Source/MQTTnet/Server/Internal/MqttClientSessionsManager.cs @@ -21,8 +21,8 @@ namespace MQTTnet.Server.Internal readonly BlockingCollection _messageQueue = new BlockingCollection(); readonly AsyncLock _createConnectionSyncRoot = new AsyncLock(); - readonly Dictionary _connections = new Dictionary(); - readonly Dictionary _sessions = new Dictionary(); + readonly Dictionary _clientConnections = new Dictionary(4096); + readonly Dictionary _clientSessions = new Dictionary(4096); readonly IDictionary _serverSessionItems = new ConcurrentDictionary(); @@ -80,7 +80,7 @@ namespace MQTTnet.Server.Internal _logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint); return null; } - + public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { MqttClientConnection clientConnection = null; @@ -95,7 +95,7 @@ namespace MQTTnet.Server.Internal } MqttConnAckPacket connAckPacket; - + var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false); if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) { @@ -127,12 +127,12 @@ namespace MQTTnet.Server.Internal { if (clientConnection.ClientId != null) { - // in case it is a takeover _connections already contains the new connection + // in case it is a takeover _clientConnections already contains the new connection if (!clientConnection.IsTakenOver) { - lock (_connections) + lock (_clientConnections) { - _connections.Remove(clientConnection.ClientId); + _clientConnections.Remove(clientConnection.ClientId); } if (!_options.EnablePersistentSessions) @@ -161,10 +161,10 @@ namespace MQTTnet.Server.Internal public async Task CloseAllConnectionsAsync() { List connections; - lock (_connections) + lock (_clientConnections) { - connections = _connections.Values.ToList(); - _connections.Clear(); + connections = _clientConnections.Values.ToList(); + _clientConnections.Clear(); } foreach (var connection in connections) @@ -175,9 +175,9 @@ namespace MQTTnet.Server.Internal public List GetConnections() { - lock (_connections) + lock (_clientConnections) { - return _connections.Values.ToList(); + return _clientConnections.Values.ToList(); } } @@ -185,9 +185,9 @@ namespace MQTTnet.Server.Internal { var result = new List(); - lock (_connections) + lock (_clientConnections) { - foreach (var connection in _connections.Values) + foreach (var connection in _clientConnections.Values) { var clientStatus = new MqttClientStatus(connection); connection.FillClientStatus(clientStatus); @@ -207,9 +207,9 @@ namespace MQTTnet.Server.Internal { var result = new List(); - lock (_sessions) + lock (_clientSessions) { - foreach (var session in _sessions.Values) + foreach (var session in _clientSessions.Values) { var sessionStatus = new MqttSessionStatus(session, this); session.FillSessionStatus(sessionStatus); @@ -228,33 +228,52 @@ namespace MQTTnet.Server.Internal _messageQueue.Add(new MqttPendingApplicationMessage(applicationMessage, sender)); } - public Task SubscribeAsync(string clientId, ICollection topicFilters) + public async Task SubscribeAsync(string clientId, ICollection topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return GetSession(clientId).SubscribeAsync(topicFilters); + if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters)); + + var fakeSubscribePacket = new MqttSubscribePacket + { + TopicFilters = topicFilters.ToList() + }; + + var clientSession = GetClientSession(clientId); + + var subscribeResult = await clientSession.SubscriptionsManager.Subscribe(fakeSubscribePacket).ConfigureAwait(false); + + foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages) + { + clientSession.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage); + } } - public Task UnsubscribeAsync(string clientId, IEnumerable topicFilters) + public Task UnsubscribeAsync(string clientId, ICollection topicFilters) { if (clientId == null) throw new ArgumentNullException(nameof(clientId)); if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - return GetSession(clientId).UnsubscribeAsync(topicFilters); + var fakeUnsubscribePacket = new MqttUnsubscribePacket + { + TopicFilters = topicFilters.ToList() + }; + + return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket); } public async Task DeleteSessionAsync(string clientId) { MqttClientConnection connection; - lock (_connections) + lock (_clientConnections) { - _connections.TryGetValue(clientId, out connection); + _clientConnections.TryGetValue(clientId, out connection); } - lock (_sessions) + lock (_clientSessions) { - _sessions.Remove(clientId); + _clientSessions.Remove(clientId); } if (connection != null) @@ -272,12 +291,12 @@ namespace MQTTnet.Server.Internal async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken) { - //make sure all queued messages are proccessed befor server stops + // Make sure all queued messages are proccessed befor server stops. while (!cancellationToken.IsCancellationRequested || _messageQueue.Any()) { try { - await TryProcessNextQueuedApplicationMessageAsync().ConfigureAwait(false); + await TryProcessNextQueuedApplicationMessage().ConfigureAwait(false); } catch (OperationCanceledException) { @@ -289,14 +308,14 @@ namespace MQTTnet.Server.Internal } } - async Task TryProcessNextQueuedApplicationMessageAsync() + async Task TryProcessNextQueuedApplicationMessage() { try { - MqttPendingApplicationMessage queuedApplicationMessage; + MqttPendingApplicationMessage pendingApplicationMessage; try { - queuedApplicationMessage = _messageQueue.Take(); + pendingApplicationMessage = _messageQueue.Take(); } catch (ArgumentNullException) { @@ -307,9 +326,9 @@ namespace MQTTnet.Server.Internal return; } - var clientConnection = queuedApplicationMessage.Sender; + var clientConnection = pendingApplicationMessage.Sender; var senderClientId = clientConnection?.ClientId ?? _options.ClientId; - var applicationMessage = queuedApplicationMessage.ApplicationMessage; + var applicationMessage = pendingApplicationMessage.ApplicationMessage; var interceptor = _options.ApplicationMessageInterceptor; if (interceptor != null) @@ -343,18 +362,41 @@ namespace MQTTnet.Server.Internal var deliveryCount = 0; List sessions; - lock (_sessions) + lock (_clientSessions) { - sessions = _sessions.Values.ToList(); + sessions = _clientSessions.Values.ToList(); } foreach (var clientSession in sessions) { - var isSubscribed = clientSession.EnqueueApplicationMessage(applicationMessage, senderClientId, false); - if (isSubscribed) + var checkSubscriptionsResult = clientSession.SubscriptionsManager.CheckSubscriptions( + applicationMessage.Topic, + applicationMessage.QualityOfServiceLevel, + senderClientId); + + if (!checkSubscriptionsResult.IsSubscribed) { - deliveryCount++; + continue; } + + _logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", clientSession.ClientId, applicationMessage.Topic); + + var queuedApplicationMessage = new MqttQueuedApplicationMessage + { + ApplicationMessage = applicationMessage, + SubscriptionQualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel, + SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers + }; + + if (checkSubscriptionsResult.RetainAsPublished) + { + // Transfer the original retain state from the publisher. + // This is a MQTTv5 feature. + queuedApplicationMessage.IsRetainedMessage = applicationMessage.Retain; + } + + clientSession.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage); + deliveryCount++; } if (deliveryCount == 0) @@ -413,9 +455,9 @@ namespace MQTTnet.Server.Internal using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { MqttClientSession session; - lock (_sessions) + lock (_clientSessions) { - if (!_sessions.TryGetValue(connectPacket.ClientId, out session)) + if (!_clientSessions.TryGetValue(connectPacket.ClientId, out session)) { _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); session = CreateSession(connectPacket.ClientId, sessionItems); @@ -433,17 +475,17 @@ namespace MQTTnet.Server.Internal } } - _sessions[connectPacket.ClientId] = session; + _clientSessions[connectPacket.ClientId] = session; } MqttClientConnection existingConnection; - lock (_connections) + lock (_clientConnections) { - _connections.TryGetValue(connectPacket.ClientId, out existingConnection); + _clientConnections.TryGetValue(connectPacket.ClientId, out existingConnection); connection = CreateConnection(connectPacket, channelAdapter, session); - _connections[connectPacket.ClientId] = connection; + _clientConnections[connectPacket.ClientId] = connection; } if (existingConnection != null) @@ -490,11 +532,11 @@ namespace MQTTnet.Server.Internal return interceptorContext; } - MqttClientSession GetSession(string clientId) + MqttClientSession GetClientSession(string clientId) { - lock (_sessions) + lock (_clientSessions) { - if (!_sessions.TryGetValue(clientId, out var session)) + if (!_clientSessions.TryGetValue(clientId, out var session)) { throw new InvalidOperationException($"Client session '{clientId}' is unknown."); } @@ -511,7 +553,6 @@ namespace MQTTnet.Server.Internal session, _options, this, - _retainedMessagesManager, _rootLogger); } @@ -522,8 +563,7 @@ namespace MQTTnet.Server.Internal sessionItems, _eventDispatcher, _options, - _retainedMessagesManager, - _rootLogger); + _retainedMessagesManager); } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs index 09f1aea3..b6c1c393 100644 --- a/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs +++ b/Source/MQTTnet/Server/Internal/MqttClientSubscriptionsManager.cs @@ -14,34 +14,41 @@ namespace MQTTnet.Server.Internal // Since writing is done by multiple threads (server API or connection thread), we cannot avoid locking // completely by swapping references etc. readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim(); - readonly Dictionary _subscriptions = new Dictionary(); + readonly Dictionary _subscriptions = new Dictionary(4096); readonly MqttClientSession _clientSession; readonly IMqttServerOptions _options; readonly MqttServerEventDispatcher _eventDispatcher; + readonly IMqttRetainedMessagesManager _retainedMessagesManager; - public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions) + public MqttClientSubscriptionsManager( + MqttClientSession clientSession, + IMqttServerOptions serverOptions, + MqttServerEventDispatcher eventDispatcher, + IMqttRetainedMessagesManager retainedMessagesManager) { _clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession)); _options = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher)); + _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); } - public async Task SubscribeAsync(MqttSubscribePacket subscribePacket, MqttConnectPacket connectPacket) + public async Task Subscribe(MqttSubscribePacket subscribePacket) { if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket)); - if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket)); - var result = new MqttClientSubscribeResult(); + var retainedApplicationMessages = await _retainedMessagesManager.GetMessagesAsync().ConfigureAwait(false); + var result = new SubscribeResult(); - foreach (var originalTopicFilter in subscribePacket.TopicFilters) + // The topic filters are order by its QoS so that the higher QoS will win over a + // lower one. + foreach (var originalTopicFilter in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel)) { - var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false); - + var interceptorContext = await InterceptSubscribe(originalTopicFilter).ConfigureAwait(false); var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter; var acceptSubscription = interceptorContext?.AcceptSubscription ?? true; var closeConnection = interceptorContext?.CloseConnection ?? false; - if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription) + if (string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription) { result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError); @@ -57,54 +64,22 @@ namespace MQTTnet.Server.Internal result.CloseConnection = true; } - if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic)) + if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic)) { continue; } - _subscriptionsLock.EnterWriteLock(); - try - { - _subscriptions[finalTopicFilter.Topic] = finalTopicFilter; - } - finally - { - _subscriptionsLock.ExitWriteLock(); - } + var subscription = CreateSubscription(finalTopicFilter, subscribePacket); await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, finalTopicFilter).ConfigureAwait(false); + + FilterRetainedApplicationMessages(retainedApplicationMessages, subscription, result); } return result; } - public async Task SubscribeAsync(IEnumerable topicFilters) - { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - foreach (var topicFilter in topicFilters) - { - var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false); - if (interceptorContext != null && !interceptorContext.AcceptSubscription) - { - continue; - } - - _subscriptionsLock.EnterWriteLock(); - try - { - _subscriptions[topicFilter.Topic] = topicFilter; - } - finally - { - _subscriptionsLock.ExitWriteLock(); - } - - await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, topicFilter).ConfigureAwait(false); - } - } - - public async Task> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket) + public async Task> Unsubscribe(MqttUnsubscribePacket unsubscribePacket) { if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket)); @@ -112,7 +87,7 @@ namespace MQTTnet.Server.Internal foreach (var topicFilter in unsubscribePacket.TopicFilters) { - var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); + var interceptorContext = await InterceptUnsubscribe(topicFilter).ConfigureAwait(false); if (interceptorContext != null && !interceptorContext.AcceptUnsubscription) { reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError); @@ -140,52 +115,50 @@ namespace MQTTnet.Server.Internal return reasonCodes; } - public async Task UnsubscribeAsync(IEnumerable topicFilters) + public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel, string senderClientId) { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - foreach (var topicFilter in topicFilters) - { - var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); - if (interceptorContext != null && !interceptorContext.AcceptUnsubscription) - { - continue; - } - - _subscriptionsLock.EnterWriteLock(); - try - { - _subscriptions.Remove(topicFilter); - } - finally - { - _subscriptionsLock.ExitWriteLock(); - } - } - } - - public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel) - { - Dictionary subscriptionsCopy; + List subscriptions; _subscriptionsLock.EnterReadLock(); try { - subscriptionsCopy = new Dictionary(_subscriptions); + subscriptions = _subscriptions.Values.ToList(); } finally { _subscriptionsLock.ExitReadLock(); } + var senderIsReceiver = string.Equals(senderClientId, _clientSession.ClientId); + var qosLevels = new HashSet(); - foreach (var subscription in subscriptionsCopy) + var subscriptionIdentifiers = new HashSet(); + var retainAsPublished = false; + + foreach (var subscription in subscriptions) { - if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key)) + if (subscription.NoLocal && senderIsReceiver) + { + // This is a MQTTv5 feature! + continue; + } + + if (subscription.RetainAsPublished) + { + // This is a MQTTv5 feature! + retainAsPublished = true; + } + + if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Topic)) { continue; } - qosLevels.Add(subscription.Value.QualityOfServiceLevel); + qosLevels.Add(subscription.QualityOfServiceLevel); + + if (subscription.Identifier > 0) + { + subscriptionIdentifiers.Add(subscription.Identifier); + } } if (qosLevels.Count == 0) @@ -193,7 +166,84 @@ namespace MQTTnet.Server.Internal return CheckSubscriptionsResult.NotSubscribed; } - return CreateSubscriptionResult(qosLevel, qosLevels); + return new CheckSubscriptionsResult + { + IsSubscribed = true, + RetainAsPublished = retainAsPublished, + SubscriptionIdentifiers = subscriptionIdentifiers.ToList(), + QualityOfServiceLevel = GetEffectiveQoS(qosLevel, qosLevels) + }; + } + + Subscription CreateSubscription(MqttTopicFilter topicFilter, MqttSubscribePacket subscribePacket) + { + var subscription = new Subscription + { + Topic = topicFilter.Topic, + NoLocal = topicFilter.NoLocal, + RetainHandling = topicFilter.RetainHandling, + RetainAsPublished = topicFilter.RetainAsPublished, + QualityOfServiceLevel = topicFilter.QualityOfServiceLevel, + Identifier = subscribePacket.Properties?.SubscriptionIdentifier ?? 0 + }; + + _subscriptionsLock.EnterWriteLock(); + try + { + subscription.IsNewSubscription = !_subscriptions.ContainsKey(topicFilter.Topic); + _subscriptions[topicFilter.Topic] = subscription; + } + finally + { + _subscriptionsLock.ExitWriteLock(); + } + + return subscription; + } + + static void FilterRetainedApplicationMessages(IList retainedApplicationMessages, Subscription subscription, SubscribeResult subscribeResult) + { + for (var i = retainedApplicationMessages.Count - 1; i >= 0; i--) + { + var retainedApplicationMessage = retainedApplicationMessages[i]; + if (retainedApplicationMessage == null) + { + continue; + } + + if (subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe) + { + // This is a MQTT V5+ feature. + continue; + } + + if (subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !subscription.IsNewSubscription) + { + // This is a MQTT V5+ feature. + continue; + } + + if (!MqttTopicFilterComparer.IsMatch(retainedApplicationMessage.Topic, subscription.Topic)) + { + continue; + } + + var queuedApplicationMessage = new MqttQueuedApplicationMessage + { + ApplicationMessage = retainedApplicationMessage, + IsRetainedMessage = true, + SubscriptionQualityOfServiceLevel = subscription.QualityOfServiceLevel + }; + + if (subscription.Identifier > 0) + { + queuedApplicationMessage.SubscriptionIdentifiers = new List { subscription.Identifier }; + } + + subscribeResult.RetainedApplicationMessages.Add(queuedApplicationMessage); + + retainedApplicationMessages[i] = null; + } } static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel) @@ -206,7 +256,7 @@ namespace MQTTnet.Server.Internal return (MqttSubscribeReasonCode)(int)qualityOfServiceLevel; } - async Task InterceptSubscribeAsync(MqttTopicFilter topicFilter) + async Task InterceptSubscribe(MqttTopicFilter topicFilter) { var interceptor = _options.SubscriptionInterceptor; if (interceptor == null) @@ -226,7 +276,7 @@ namespace MQTTnet.Server.Internal return context; } - async Task InterceptUnsubscribeAsync(string topicFilter) + async Task InterceptUnsubscribe(string topicFilter) { var interceptor = _options.UnsubscriptionInterceptor; if (interceptor == null) @@ -246,7 +296,7 @@ namespace MQTTnet.Server.Internal return context; } - static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet subscribedQoSLevels) + static MqttQualityOfServiceLevel GetEffectiveQoS(MqttQualityOfServiceLevel qosLevel, HashSet subscribedQoSLevels) { MqttQualityOfServiceLevel effectiveQoS; if (subscribedQoSLevels.Contains(qosLevel)) @@ -262,11 +312,7 @@ namespace MQTTnet.Server.Internal effectiveQoS = subscribedQoSLevels.Max(); } - return new CheckSubscriptionsResult - { - IsSubscribed = true, - QualityOfServiceLevel = effectiveQoS - }; + return effectiveQoS; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/Internal/MqttRetainedMessagesManager.cs b/Source/MQTTnet/Server/Internal/MqttRetainedMessagesManager.cs index a0afabd8..e6979eac 100644 --- a/Source/MQTTnet/Server/Internal/MqttRetainedMessagesManager.cs +++ b/Source/MQTTnet/Server/Internal/MqttRetainedMessagesManager.cs @@ -12,11 +12,11 @@ namespace MQTTnet.Server.Internal public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager { readonly AsyncLock _storageAccessLock = new AsyncLock(); - readonly Dictionary _messages = new Dictionary(); + readonly Dictionary _messages = new Dictionary(4096); IMqttNetScopedLogger _logger; IMqttServerOptions _options; - + // TODO: Get rid of the logger here! public Task Start(IMqttServerOptions options, IMqttNetLogger logger) { @@ -116,36 +116,7 @@ namespace MQTTnet.Server.Internal _logger.Error(exception, "Unhandled exception while handling retained messages."); } } - - public Task> GetSubscribedMessagesAsync(ICollection topicFilters) - { - if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters)); - - var matchingRetainedMessages = new List(); - - List retainedMessages; - lock (_messages) - { - retainedMessages = _messages.Values.ToList(); - } - - foreach (var retainedMessage in retainedMessages) - { - foreach (var topicFilter in topicFilters) - { - if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic)) - { - continue; - } - - matchingRetainedMessages.Add(retainedMessage); - break; - } - } - - return Task.FromResult((IList)matchingRetainedMessages); - } - + public Task> GetMessagesAsync() { lock (_messages) diff --git a/Source/MQTTnet/Server/Internal/MqttServerKeepAliveMonitor.cs b/Source/MQTTnet/Server/Internal/MqttServerKeepAliveMonitor.cs index 629485f2..e41acbda 100644 --- a/Source/MQTTnet/Server/Internal/MqttServerKeepAliveMonitor.cs +++ b/Source/MQTTnet/Server/Internal/MqttServerKeepAliveMonitor.cs @@ -76,7 +76,7 @@ namespace MQTTnet.Server.Internal return; } - if (connection.ConnectPacket.KeepAlivePeriod == 0) + if (connection.KeepAlivePeriod == 0) { // The keep alive feature is not used by the current connection. return; @@ -92,7 +92,7 @@ namespace MQTTnet.Server.Internal // Values described here: [MQTT-3.1.2-24]. // If the client sends 5 sec. the server will allow up to 7.5 seconds. // If the client sends 1 sec. the server will allow up to 1.5 seconds. - var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D; + var maxDurationWithoutPacket = connection.KeepAlivePeriod * 1.5D; var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds; if (secondsWithoutPackage < maxDurationWithoutPacket) diff --git a/Source/MQTTnet/Server/Internal/SubscribeResult.cs b/Source/MQTTnet/Server/Internal/SubscribeResult.cs new file mode 100644 index 00000000..2a3f070c --- /dev/null +++ b/Source/MQTTnet/Server/Internal/SubscribeResult.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using MQTTnet.Protocol; + +namespace MQTTnet.Server.Internal +{ + public sealed class SubscribeResult + { + public List ReturnCodes { get; } = new List(128); + + public List ReasonCodes { get; } = new List(128); + + public List RetainedApplicationMessages { get; } = new List(1024); + + public bool CloseConnection { get; set; } + } +} diff --git a/Source/MQTTnet/Server/Internal/Subscription.cs b/Source/MQTTnet/Server/Internal/Subscription.cs new file mode 100644 index 00000000..965cce67 --- /dev/null +++ b/Source/MQTTnet/Server/Internal/Subscription.cs @@ -0,0 +1,21 @@ +using MQTTnet.Protocol; + +namespace MQTTnet.Server.Internal +{ + public sealed class Subscription + { + public string Topic { get; set; } + + public bool NoLocal { get; set; } + + public MqttRetainHandling RetainHandling { get; set; } + + public bool RetainAsPublished { get; set; } + + public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; } + + public uint Identifier { get; set; } + + public bool IsNewSubscription { get; set; } + } +} \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttClientSubscribeResult.cs b/Source/MQTTnet/Server/MqttClientSubscribeResult.cs deleted file mode 100644 index f25d6c74..00000000 --- a/Source/MQTTnet/Server/MqttClientSubscribeResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using MQTTnet.Protocol; - -namespace MQTTnet.Server -{ - public sealed class MqttClientSubscribeResult - { - public List ReturnCodes { get; } = new List(); - - public List ReasonCodes { get; } = new List(); - - public bool CloseConnection { get; set; } - } -} diff --git a/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs b/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs index 57d3952c..c0eb8a21 100644 --- a/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs +++ b/Source/MQTTnet/Server/MqttConnectionValidatorContext.cs @@ -17,7 +17,7 @@ namespace MQTTnet.Server public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter) { - _connectPacket = connectPacket; + _connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket)); _clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter)); } @@ -35,9 +35,9 @@ namespace MQTTnet.Server public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion; - public string Username => _connectPacket?.Username; + public string Username => _connectPacket.Username; - public byte[] RawPassword => _connectPacket?.Password; + public byte[] RawPassword => _connectPacket.Password; public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray); @@ -45,7 +45,7 @@ namespace MQTTnet.Server /// Gets or sets the will delay interval. /// This is the time between the client disconnect and the time the will message will be sent. /// - public MqttApplicationMessage WillMessage => _connectPacket?.WillMessage; + public MqttApplicationMessage WillMessage => _connectPacket.WillMessage; /// /// Gets or sets a value indicating whether clean sessions are used or not. @@ -55,7 +55,7 @@ namespace MQTTnet.Server /// It can also connect as a durable client using a persistent connection. /// In this mode, the broker will store subscription information, and undelivered messages for the client. /// - public bool? CleanSession => _connectPacket?.CleanSession; + public bool? CleanSession => _connectPacket.CleanSession; /// /// Gets or sets the keep alive period. @@ -64,7 +64,7 @@ namespace MQTTnet.Server /// This message exchange confirms that the connection is open and working. /// This period is known as the keep alive period. /// - public ushort? KeepAlivePeriod => _connectPacket?.KeepAlivePeriod; + public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod; /// /// Gets or sets the user properties. @@ -73,57 +73,57 @@ namespace MQTTnet.Server /// The feature is very similar to the HTTP header concept. /// Hint: MQTT 5 feature only. /// - public List UserProperties => _connectPacket?.Properties?.UserProperties; + public List UserProperties => _connectPacket.Properties?.UserProperties; /// /// Gets or sets the authentication data. /// Hint: MQTT 5 feature only. /// - public byte[] AuthenticationData => _connectPacket?.Properties?.AuthenticationData; + public byte[] AuthenticationData => _connectPacket.Properties?.AuthenticationData; /// /// Gets or sets the authentication method. /// Hint: MQTT 5 feature only. /// - public string AuthenticationMethod => _connectPacket?.Properties?.AuthenticationMethod; + public string AuthenticationMethod => _connectPacket.Properties?.AuthenticationMethod; - public uint? MaximumPacketSize => _connectPacket?.Properties?.MaximumPacketSize; + public uint? MaximumPacketSize => _connectPacket.Properties?.MaximumPacketSize; /// /// Gets or sets the receive maximum. /// This gives the maximum length of the receive messages. /// - public ushort? ReceiveMaximum => _connectPacket?.Properties?.ReceiveMaximum; + public ushort? ReceiveMaximum => _connectPacket.Properties?.ReceiveMaximum; /// /// Gets or sets the topic alias maximum. /// This gives the maximum length of the topic alias. /// - public ushort? TopicAliasMaximum => _connectPacket?.Properties?.TopicAliasMaximum; + public ushort TopicAliasMaximum => _connectPacket.Properties?.TopicAliasMaximum ?? 0; /// /// Gets the request problem information. /// Hint: MQTT 5 feature only. /// - public bool? RequestProblemInformation => _connectPacket?.Properties?.RequestProblemInformation; + public bool? RequestProblemInformation => _connectPacket.Properties?.RequestProblemInformation; /// /// Gets the request response information. /// Hint: MQTT 5 feature only. /// - public bool? RequestResponseInformation => _connectPacket?.Properties?.RequestResponseInformation; + public bool? RequestResponseInformation => _connectPacket.Properties?.RequestResponseInformation; /// /// Gets the session expiry interval. /// The time after a session expires when it's not actively used. /// - public uint? SessionExpiryInterval => _connectPacket?.Properties?.SessionExpiryInterval; + public uint? SessionExpiryInterval => _connectPacket.Properties?.SessionExpiryInterval; /// /// Gets or sets the will delay interval. /// This is the time between the client disconnect and the time the will message will be sent. /// - public uint? WillDelayInterval => _connectPacket?.Properties?.WillDelayInterval; + public uint? WillDelayInterval => _connectPacket.Properties?.WillDelayInterval; /// /// Gets or sets a key/value collection that can be used to share data within the scope of this session. @@ -144,7 +144,7 @@ namespace MQTTnet.Server /// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is /// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be /// converted properly. - /// Hint: MQTT 5 feature only. + /// MQTTv5 only. /// public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success; @@ -159,12 +159,23 @@ namespace MQTTnet.Server /// /// Gets or sets the response authentication data. - /// Hint: MQTT 5 feature only. + /// MQTTv5 only. /// public byte[] ResponseAuthenticationData { get; set; } + /// + /// Gets or sets the assigned client identifier. + /// MQTTv5 only. + /// public string AssignedClientIdentifier { get; set; } public string ReasonString { get; set; } + + /// + /// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send + /// a different server address to the client. + /// MQTTv5 only. + /// + public string ServerReference { get; set; } } } diff --git a/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs b/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs index 8ffd55f0..b31ea848 100644 --- a/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs +++ b/Source/MQTTnet/Server/MqttQueuedApplicationMessage.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections.Generic; using MQTTnet.Protocol; namespace MQTTnet.Server @@ -11,6 +11,10 @@ namespace MQTTnet.Server public bool IsRetainedMessage { get; set; } + public List SubscriptionIdentifiers { get; set; } + + public bool IsDuplicate { get; set; } + /// /// Gets or sets the subscription quality of service level. /// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message. @@ -20,14 +24,5 @@ namespace MQTTnet.Server /// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once). /// public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; } - - [Obsolete("Use 'SubscriptionQualityOfServiceLevel' instead.")] - public MqttQualityOfServiceLevel QualityOfServiceLevel - { - get => SubscriptionQualityOfServiceLevel; - set => SubscriptionQualityOfServiceLevel = value; - } - - public bool IsDuplicate { get; set; } } } \ No newline at end of file diff --git a/Source/MQTTnet/Server/MqttServerOptions.cs b/Source/MQTTnet/Server/MqttServerOptions.cs index 101181c6..28233657 100644 --- a/Source/MQTTnet/Server/MqttServerOptions.cs +++ b/Source/MQTTnet/Server/MqttServerOptions.cs @@ -3,7 +3,7 @@ using MQTTnet.Server.Internal; namespace MQTTnet.Server { - public class MqttServerOptions : IMqttServerOptions + public sealed class MqttServerOptions : IMqttServerOptions { public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions(); diff --git a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs index 4d05314c..710f2263 100644 --- a/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs +++ b/Tests/MQTTnet.AspNetCore.Tests/MqttConnectionContextTest.cs @@ -17,6 +17,7 @@ using MQTTnet.Packets; using System.Net; using MQTTnet.AspNetCore.Extensions; using MQTTnet.Protocol; +using MQTTnet.Tests.Extensions; namespace MQTTnet.AspNetCore.Tests { diff --git a/Tests/MQTTnet.Core.Tests/BaseTestClass.cs b/Tests/MQTTnet.Core.Tests/BaseTestClass.cs index f7ab7a6b..80cfe240 100644 --- a/Tests/MQTTnet.Core.Tests/BaseTestClass.cs +++ b/Tests/MQTTnet.Core.Tests/BaseTestClass.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; namespace MQTTnet.Tests @@ -9,12 +10,12 @@ namespace MQTTnet.Tests { public TestContext TestContext { get; set; } - protected TestEnvironment CreateTestEnvironment() + protected TestEnvironment CreateTestEnvironment(MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { - return new TestEnvironment(TestContext); + return new TestEnvironment(TestContext, protocolVersion); } - protected Task LongDelay() + protected Task LongTestDelay() { return Task.Delay(TimeSpan.FromSeconds(1)); } diff --git a/Tests/MQTTnet.Core.Tests/Extensions/MqttPacketWriterExtensions.cs b/Tests/MQTTnet.Core.Tests/Extensions/MqttPacketWriterExtensions.cs index c10ccb17..1e0473b2 100644 --- a/Tests/MQTTnet.Core.Tests/Extensions/MqttPacketWriterExtensions.cs +++ b/Tests/MQTTnet.Core.Tests/Extensions/MqttPacketWriterExtensions.cs @@ -1,6 +1,7 @@ -using MQTTnet.Protocol; +using MQTTnet.Formatter; +using MQTTnet.Protocol; -namespace MQTTnet.Formatter +namespace MQTTnet.Tests.Extensions { public static class MqttPacketWriterExtensions { diff --git a/Tests/MQTTnet.Core.Tests/LowLevelMqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/LowLevelMqttClient_Tests.cs index 4bc9c43b..40ce57de 100644 --- a/Tests/MQTTnet.Core.Tests/LowLevelMqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/LowLevelMqttClient_Tests.cs @@ -36,7 +36,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var factory = new MqttFactory(); var lowLevelClient = factory.CreateLowLevelMqttClient(); @@ -52,7 +52,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var factory = new MqttFactory(); var lowLevelClient = factory.CreateLowLevelMqttClient(); @@ -73,7 +73,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var factory = new MqttFactory(); var lowLevelClient = factory.CreateLowLevelMqttClient(); @@ -97,8 +97,8 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.ServerPort = 8364; - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectLowLevelClientAsync(o => o.WithCommunicationTimeout(TimeSpan.Zero)); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectLowLevelClient(o => o.WithCommunicationTimeout(TimeSpan.Zero)); await Authenticate(client).ConfigureAwait(false); diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs index 94b42eb0..11a20ac4 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Client_Tests.cs @@ -25,10 +25,10 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); // This test can be also executed against "broker.hivemq.com" to validate package format. - var client = await testEnvironment.ConnectClientAsync( + var client = await testEnvironment.ConnectClient( new MqttClientOptionsBuilder() //.WithTcpServer("broker.hivemq.com") .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) @@ -61,72 +61,13 @@ namespace MQTTnet.Tests.MQTTv5 } } - [TestMethod] - public async Task Connect_With_AssignedClientId() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - string serverConnectedClientId = null; - string serverDisconnectedClientId = null; - string clientAssignedClientId = null; - - // Arrange server - var disconnectedMre = new ManualResetEventSlim(); - var serverOptions = new MqttServerOptionsBuilder() - .WithConnectionValidator((context) => - { - if (string.IsNullOrEmpty(context.ClientId)) - { - context.AssignedClientIdentifier = "test123"; - context.ReasonCode = MqttConnectReasonCode.Success; - } - }); - await testEnvironment.StartServerAsync(serverOptions); - testEnvironment.Server.UseClientConnectedHandler((args) => - { - serverConnectedClientId = args.ClientId; - }); - testEnvironment.Server.UseClientDisconnectedHandler((args) => - { - serverDisconnectedClientId = args.ClientId; - disconnectedMre.Set(); - }); - - // Arrange client - var client = testEnvironment.CreateClient(); - client.UseConnectedHandler((args) => - { - clientAssignedClientId = args.AuthenticateResult.AssignedClientIdentifier; - }); - - // Act - await client.ConnectAsync(new MqttClientOptionsBuilder() - .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) - .WithProtocolVersion(MqttProtocolVersion.V500) - .WithClientId(null) - .Build()); - await client.DisconnectAsync(); - - // Wait for ClientDisconnectedHandler to trigger - disconnectedMre.Wait(500); - - // Assert - Assert.IsNotNull(serverConnectedClientId); - Assert.IsNotNull(serverDisconnectedClientId); - Assert.IsNotNull(clientAssignedClientId); - Assert.AreEqual("test123", serverConnectedClientId); - Assert.AreEqual("test123", serverDisconnectedClientId); - Assert.AreEqual("test123", clientAssignedClientId); - } - } - [TestMethod] public async Task Connect() { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); - await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500).Build()); + await testEnvironment.StartServer(); + await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).Build()); } } @@ -135,9 +76,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); await client.DisconnectAsync(); } } @@ -147,10 +88,10 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var client = - await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var result = await client.SubscribeAsync(new MqttClientSubscribeOptions() { @@ -173,9 +114,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); await client.SubscribeAsync("a"); var result = await client.UnsubscribeAsync("a"); @@ -191,9 +132,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var result = await client.PublishAsync("a", "b"); await client.DisconnectAsync(); @@ -206,9 +147,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var result = await client.PublishAsync("a", "b", MqttQualityOfServiceLevel.AtLeastOnce); await client.DisconnectAsync(); @@ -221,9 +162,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var result = await client.PublishAsync("a", "b", MqttQualityOfServiceLevel.ExactlyOnce); await client.DisconnectAsync(); @@ -236,9 +177,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic("Hello") @@ -265,11 +206,11 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var receivedMessages = new List(); - var client1 = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1")); + var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client1")); client1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => { lock (receivedMessages) @@ -280,7 +221,7 @@ namespace MQTTnet.Tests.MQTTv5 await client1.SubscribeAsync("a"); - var client2 = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client2")); + var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500).WithClientId("client2")); await client2.PublishAsync("a", "b"); await Task.Delay(500); @@ -300,9 +241,9 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var receiver = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var receiver = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); await receiver.SubscribeAsync("#"); MqttApplicationMessage receivedMessage = null; @@ -311,7 +252,7 @@ namespace MQTTnet.Tests.MQTTv5 receivedMessage = c.ApplicationMessage; }); - var sender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var sender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); var applicationMessage = new MqttApplicationMessageBuilder() .WithTopic("Hello") diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs index 43b923d9..be589d5e 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MQTTv5/Server_Tests.cs @@ -20,17 +20,17 @@ namespace MQTTnet.Tests.MQTTv5 { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage).WithProtocolVersion(MqttProtocolVersion.V500); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + var c2 = await testEnvironment.ConnectClient(clientOptions); c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. await Task.Delay(1000); diff --git a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs index 636e998c..29a9df55 100644 --- a/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/ManagedMqttClient_Tests.cs @@ -60,7 +60,7 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var clientOptions = new MqttClientOptionsBuilder() @@ -72,7 +72,7 @@ namespace MQTTnet.Tests .WithClientOptions(clientOptions) .Build()); - var recievingClient = await testEnvironment.ConnectClientAsync(); + var recievingClient = await testEnvironment.ConnectClient(); await recievingClient.SubscribeAsync("My/last/will"); recievingClient.UseApplicationMessageReceivedHandler(context => Interlocked.Increment(ref receivedMessagesCount)); @@ -90,7 +90,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetLogger()); var clientOptions = new MqttClientOptionsBuilder() @@ -120,7 +120,7 @@ namespace MQTTnet.Tests testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreServerLogErrors = true; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetLogger()); var clientOptions = new MqttClientOptionsBuilder() @@ -185,7 +185,7 @@ namespace MQTTnet.Tests //wait a bit for the subscriptions to become established before the messages are published await Task.Delay(500); - var sendingClient = await testEnvironment.ConnectClientAsync(); + var sendingClient = await testEnvironment.ConnectClient(); async Task PublishMessages() { @@ -234,7 +234,7 @@ namespace MQTTnet.Tests { var managedClient = await CreateManagedClientAsync(testEnvironment); - var sendingClient = await testEnvironment.ConnectClientAsync(); + var sendingClient = await testEnvironment.ConnectClient(); await managedClient.SubscribeAsync("topic"); @@ -269,7 +269,7 @@ namespace MQTTnet.Tests // do not depend on the connection check interval anymore var connectionCheckInterval = TimeSpan.FromSeconds(10); var managedClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval); - var sendingClient = await testEnvironment.ConnectClientAsync(); + var sendingClient = await testEnvironment.ConnectClient(); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true }); @@ -292,9 +292,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync().ConfigureAwait(false); + await testEnvironment.StartServer().ConfigureAwait(false); - var sendingClient = await testEnvironment.ConnectClientAsync().ConfigureAwait(false); + var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false); await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", @@ -360,7 +360,7 @@ namespace MQTTnet.Tests testEnvironment.IgnoreClientLogErrors = true; var serverOptions = new MqttServerOptionsBuilder(); - await testEnvironment.StartServerAsync(serverOptions); + await testEnvironment.StartServer(serverOptions); var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5)); @@ -384,7 +384,7 @@ namespace MQTTnet.Tests await Task.Delay(10000); // over 1.5T var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10)); - var sendClient = await testEnvironment.ConnectClientAsync(option2); + var sendClient = await testEnvironment.ConnectClient(option2); await sendClient.PublishAsync("aaa", "1"); await Task.Delay(3000); @@ -398,7 +398,7 @@ namespace MQTTnet.Tests try { - var sendClient = await testEnvironment.ConnectClientAsync(options); + var sendClient = await testEnvironment.ConnectClient(options); sendClient.ApplicationMessageReceivedHandler = new MQTTnet.Client.Receiving.MqttApplicationMessageReceivedHandlerDelegate(e => { onReceive(); @@ -417,7 +417,7 @@ namespace MQTTnet.Tests IMqttClient underlyingClient = null, TimeSpan? connectionCheckInterval = null) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var clientOptions = new MqttClientOptionsBuilder() .WithTcpServer("localhost", testEnvironment.ServerPort); diff --git a/Tests/MQTTnet.Core.Tests/Mockups/MqttPacketAsserts.cs b/Tests/MQTTnet.Core.Tests/Mockups/MqttPacketAsserts.cs new file mode 100644 index 00000000..4fbbdc08 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Mockups/MqttPacketAsserts.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Packets; + +namespace MQTTnet.Tests.Mockups +{ + public sealed class MqttPacketAsserts + { + public void AssertIsConnectPacket(MqttBasePacket packet) + { + Assert.AreEqual(packet.GetType(), typeof(MqttConnectPacket)); + } + + public void AssertIsConnAckPacket(MqttBasePacket packet) + { + Assert.AreEqual(packet.GetType(), typeof(MqttConnAckPacket)); + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestApplicationMessageReceivedHandler.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestApplicationMessageReceivedHandler.cs new file mode 100644 index 00000000..eda3cc39 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestApplicationMessageReceivedHandler.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client.Receiving; +using MQTTnet.Implementations; + +namespace MQTTnet.Tests.Mockups +{ + public sealed class TestApplicationMessageReceivedHandler : IMqttApplicationMessageReceivedHandler + { + readonly List _messageReceivedEventArgs = new List(); + + public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + { + lock (_messageReceivedEventArgs) + { + _messageReceivedEventArgs.Add(eventArgs.ApplicationMessage); + } + + return PlatformAbstractionLayer.CompletedTask; + } + + public List ReceivedApplicationMessages + { + get + { + lock (_messageReceivedEventArgs) + { + return _messageReceivedEventArgs.ToList(); + } + } + } + + public void AssertReceivedCountEquals(int expectedCount) + { + lock (_messageReceivedEventArgs) + { + Assert.AreEqual(expectedCount, _messageReceivedEventArgs.Count); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs index 5fd449db..bbe4a2fd 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestClientWrapper.cs @@ -47,7 +47,7 @@ namespace MQTTnet.Tests.Mockups set => Implementation.ApplicationMessageReceivedHandler = value; } - public Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) + public Task ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken) { if (TestContext != null) { diff --git a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs index 64026b5f..df07ebfb 100644 --- a/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs +++ b/Tests/MQTTnet.Core.Tests/Mockups/TestEnvironment.cs @@ -11,13 +11,14 @@ using System.Threading; using System.Threading.Tasks; using MQTTnet.Extensions.Rpc; using MQTTnet.Extensions.Rpc.Options; +using MQTTnet.Formatter; using MQTTnet.LowLevelClient; namespace MQTTnet.Tests.Mockups { public sealed class TestEnvironment : IDisposable { - readonly MqttFactory _mqttFactory = new MqttFactory(); + readonly MqttProtocolVersion _protocolVersion; readonly List _lowLevelClients = new List(); readonly List _clients = new List(); readonly List _serverErrors = new List(); @@ -25,6 +26,8 @@ namespace MQTTnet.Tests.Mockups readonly List _exceptions = new List(); + public MqttFactory Factory { get; } = new MqttFactory(); + public IMqttServer Server { get; private set; } public bool IgnoreClientLogErrors { get; set; } @@ -43,8 +46,9 @@ namespace MQTTnet.Tests.Mockups { } - public TestEnvironment(TestContext testContext) + public TestEnvironment(TestContext testContext, MqttProtocolVersion protocolVersion = MqttProtocolVersion.V311) { + _protocolVersion = protocolVersion; TestContext = testContext; ServerLogger.LogMessagePublished += (s, e) => @@ -84,24 +88,26 @@ namespace MQTTnet.Tests.Mockups { lock (_clients) { - var client = _mqttFactory.CreateMqttClient(ClientLogger); + var client = Factory.CreateMqttClient(ClientLogger); _clients.Add(client); return new TestClientWrapper(client, TestContext); } } - public Task ConnectClientAsync() + public Task ConnectClient() { - return ConnectClientAsync(new MqttClientOptionsBuilder()); + return ConnectClient(Factory.CreateClientOptionsBuilder().WithProtocolVersion(_protocolVersion)); } - public async Task ConnectClientAsync(Action optionsBuilder) + public async Task ConnectClient(Action optionsBuilder) { if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder)); - var options = new MqttClientOptionsBuilder(); - options = options.WithTcpServer("localhost", ServerPort); + var options = Factory.CreateClientOptionsBuilder() + .WithProtocolVersion(_protocolVersion) + .WithTcpServer("127.0.0.1", ServerPort); + optionsBuilder.Invoke(options); var client = CreateClient(); @@ -110,11 +116,11 @@ namespace MQTTnet.Tests.Mockups return client; } - public async Task ConnectClientAsync(MqttClientOptionsBuilder options) + public async Task ConnectClient(MqttClientOptionsBuilder options) { if (options == null) throw new ArgumentNullException(nameof(options)); - options = options.WithTcpServer("localhost", ServerPort); + options = options.WithTcpServer("127.0.0.1", ServerPort); var client = CreateClient(); await client.ConnectAsync(options.Build()).ConfigureAwait(false); @@ -122,7 +128,7 @@ namespace MQTTnet.Tests.Mockups return client; } - public async Task ConnectClientAsync(IMqttClientOptions options) + public async Task ConnectClient(IMqttClientOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -136,19 +142,19 @@ namespace MQTTnet.Tests.Mockups { lock (_clients) { - var client = _mqttFactory.CreateLowLevelMqttClient(ClientLogger); + var client = Factory.CreateLowLevelMqttClient(ClientLogger); _lowLevelClients.Add(client); return client; } } - public Task ConnectLowLevelClientAsync() + public Task ConnectLowLevelClient() { - return ConnectLowLevelClientAsync(o => { }); + return ConnectLowLevelClient(o => { }); } - public async Task ConnectLowLevelClientAsync(Action optionsBuilder) + public async Task ConnectLowLevelClient(Action optionsBuilder) { if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder)); @@ -162,14 +168,9 @@ namespace MQTTnet.Tests.Mockups return client; } - public async Task ConnectRpcClientAsync(IMqttRpcClientOptions options) + public async Task ConnectRpcClient(IMqttRpcClientOptions options) { - return new MqttRpcClient(await ConnectClientAsync(), options); - } - - public Task StartServerAsync() - { - return StartServerAsync(new MqttServerOptionsBuilder()); + return new MqttRpcClient(await ConnectClient(), options); } public IMqttServer CreateServer() @@ -179,11 +180,16 @@ namespace MQTTnet.Tests.Mockups throw new InvalidOperationException("Server already started."); } - Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this); + Server = new TestServerWrapper(Factory.CreateMqttServer(ServerLogger), TestContext, this); return Server; } + + public Task StartServer() + { + return StartServer(Factory.CreateServerOptionsBuilder()); + } - public async Task StartServerAsync(MqttServerOptionsBuilder options) + public async Task StartServer(MqttServerOptionsBuilder options) { CreateServer(); @@ -194,6 +200,36 @@ namespace MQTTnet.Tests.Mockups return Server; } + + public async Task StartServer(Action options) + { + CreateServer(); + + var optionsBuilder = Factory.CreateServerOptionsBuilder(); + optionsBuilder.WithDefaultEndpointPort(ServerPort); + optionsBuilder.WithMaxPendingMessagesPerClient(int.MaxValue); + + options?.Invoke(optionsBuilder); + + await Server.StartAsync(optionsBuilder.Build()); + + return Server; + } + + public TestApplicationMessageReceivedHandler CreateApplicationMessageHandler(IMqttClient mqttClient) + { + if (mqttClient == null) throw new ArgumentNullException(nameof(mqttClient)); + + var handler = new TestApplicationMessageReceivedHandler(); + if (mqttClient.ApplicationMessageReceivedHandler != null) + { + throw new InvalidOperationException("ApplicationMessageReceivedHandler is already set."); + } + + mqttClient.ApplicationMessageReceivedHandler = handler; + + return handler; + } public void ThrowIfLogErrors() { diff --git a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs index 33b88c84..c1efd497 100644 --- a/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttClient_Tests.cs @@ -31,8 +31,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectLowLevelClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectLowLevelClient(); var i = 0; server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c => @@ -67,8 +67,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); Assert.IsTrue(client.IsConnected); client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected)); @@ -83,8 +83,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); Assert.IsTrue(client.IsConnected); client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected)); @@ -123,8 +123,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); await client.PingAsync(CancellationToken.None); } @@ -135,8 +135,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); await client.SubscribeAsync("#"); @@ -169,9 +169,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); - var client1 = await testEnvironment.ConnectClientAsync(); - var client2 = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var client1 = await testEnvironment.ConnectClient(); + var client2 = await testEnvironment.ConnectClient(); await client1.SubscribeAsync("#"); await client2.SubscribeAsync("#"); @@ -214,8 +214,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); await Task.Delay(500); Assert.IsTrue(client.IsConnected); @@ -239,8 +239,8 @@ namespace MQTTnet.Tests { testEnvironment.IgnoreClientLogErrors = true; - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); await Task.Delay(500); Assert.IsTrue(client.IsConnected); @@ -314,8 +314,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); var result = await client.PublishAsync("a", "a", MqttQualityOfServiceLevel.AtMostOnce); Assert.AreEqual(null, result.PacketIdentifier); @@ -393,8 +393,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + var server = await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); var handlerFired = false; client.UseDisconnectedHandler(e => handlerFired = true); @@ -445,9 +445,9 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); await client1.SubscribeAsync("x"); var receivedValues = new List(); @@ -465,7 +465,7 @@ namespace MQTTnet.Tests client1.UseApplicationMessageReceivedHandler(Handler1); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); for (var i = MessagesCount; i > 0; i--) { await client2.PublishAsync("x", i.ToString()); @@ -490,9 +490,9 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.ExactlyOnce); var receivedValues = new List(); @@ -514,7 +514,7 @@ namespace MQTTnet.Tests client1.UseApplicationMessageReceivedHandler(Handler1); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); for (var i = MessagesCount; i > 0; i--) { await client2.PublishAsync("x", i.ToString(), MqttQualityOfServiceLevel.ExactlyOnce); @@ -534,9 +534,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); await client1.SubscribeAsync("request/+"); async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs) @@ -546,7 +546,7 @@ namespace MQTTnet.Tests client1.UseApplicationMessageReceivedHandler(Handler1); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); await client2.SubscribeAsync("reply/#"); var replies = new List(); @@ -578,11 +578,11 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var receivedMessages = new List(); - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); client1.UseApplicationMessageReceivedHandler(c => { lock (receivedMessages) @@ -593,7 +593,7 @@ namespace MQTTnet.Tests await client1.SubscribeAsync("a"); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithRetainFlag().Build(); await client2.PublishAsync(message); @@ -609,13 +609,13 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); const string client1Topic = "client1/topic"; const string client2Topic = "client2/topic"; const string expectedClient2Message = "hello client2"; - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); client1.UseApplicationMessageReceivedHandler(async c => { await client1.PublishAsync(client2Topic, expectedClient2Message, MqttQualityOfServiceLevel.AtLeastOnce); @@ -623,7 +623,7 @@ namespace MQTTnet.Tests await client1.SubscribeAsync(client1Topic, MqttQualityOfServiceLevel.AtLeastOnce); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); var client2TopicResults = new List(); @@ -634,7 +634,7 @@ namespace MQTTnet.Tests await client2.SubscribeAsync(client2Topic); - var client3 = await testEnvironment.ConnectClientAsync(); + var client3 = await testEnvironment.ConnectClient(); var message = new MqttApplicationMessageBuilder().WithTopic(client1Topic).Build(); await client3.PublishAsync(message); await client3.PublishAsync(message); @@ -652,7 +652,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var receivedMessages = new List(); @@ -694,12 +694,12 @@ namespace MQTTnet.Tests testEnvironment.IgnoreClientLogErrors = true; testEnvironment.IgnoreServerLogErrors = true; - await testEnvironment.StartServerAsync( + await testEnvironment.StartServer( new MqttServerOptionsBuilder() .WithPersistentSessions() .WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(250))); - var client1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithCleanSession(false)); + var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithCleanSession(false)); await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.AtLeastOnce); var retries = 0; @@ -714,7 +714,7 @@ namespace MQTTnet.Tests client1.UseApplicationMessageReceivedHandler(Handler1); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); await client2.PublishAsync("x"); await Task.Delay(3000); @@ -735,9 +735,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); Assert.IsTrue(client.IsConnected); } @@ -748,8 +748,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var client = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var client = await testEnvironment.ConnectClient(); Assert.IsTrue(client.IsConnected); await client.DisconnectAsync(); @@ -763,12 +763,12 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var clients = new List(); for (var i = 0; i < 100; i++) { - clients.Add(await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("a"))); + clients.Add(await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a"))); } await Task.Delay(500); @@ -795,7 +795,7 @@ namespace MQTTnet.Tests await receiveClient.SubscribeAsync("x"); - var sendClient = await testEnvironment.ConnectClientAsync(); + var sendClient = await testEnvironment.ConnectClient(); await sendClient.PublishAsync("x", "1"); await Task.Delay(250); @@ -809,10 +809,10 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClientAsync(); - var receiver = await testEnvironment.ConnectClientAsync(); + var sender = await testEnvironment.ConnectClient(); + var receiver = await testEnvironment.ConnectClient(); var message = new MqttApplicationMessageBuilder() .WithTopic("A"); @@ -840,9 +840,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); - var client1 = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); - var client2 = await testEnvironment.ConnectClientAsync(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + await testEnvironment.StartServer(); + var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); + var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500)); var disconnectedFired = false; client1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(c => diff --git a/Tests/MQTTnet.Core.Tests/MqttPacketSerializer_Tests.cs b/Tests/MQTTnet.Core.Tests/MqttPacketSerializer_Tests.cs index e51c911c..6b4124b3 100644 --- a/Tests/MQTTnet.Core.Tests/MqttPacketSerializer_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/MqttPacketSerializer_Tests.cs @@ -14,6 +14,7 @@ using MQTTnet.Formatter.V5; using MQTTnet.Internal; using MQTTnet.Packets; using MQTTnet.Protocol; +using MQTTnet.Tests.Extensions; namespace MQTTnet.Tests { diff --git a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs index 154f848e..1d472b6d 100644 --- a/Tests/MQTTnet.Core.Tests/RPC_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/RPC_Tests.cs @@ -62,9 +62,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var requestSender = await testEnvironment.ConnectClientAsync(); + var requestSender = await testEnvironment.ConnectClient(); var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build()); await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); @@ -77,11 +77,11 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var requestSender = await testEnvironment.ConnectClientAsync(); + var requestSender = await testEnvironment.ConnectClient(); - var rpcClient = await testEnvironment.ConnectRpcClientAsync(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()) .Build()); + var rpcClient = await testEnvironment.ConnectRpcClient(new MqttRpcClientOptionsBuilder().WithTopicGenerationStrategy(new TestTopicStrategy()) .Build()); await rpcClient.ExecuteAsync(TimeSpan.FromSeconds(2), "ping", "", MqttQualityOfServiceLevel.AtMostOnce); } @@ -91,8 +91,8 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var responseSender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion)); + await testEnvironment.StartServer(); + var responseSender = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithProtocolVersion(protocolVersion)); await responseSender.SubscribeAsync("MQTTnet.RPC/+/ping", qosLevel); responseSender.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(async e => @@ -100,7 +100,7 @@ namespace MQTTnet.Tests await responseSender.PublishAsync(e.ApplicationMessage.Topic + "/response", "pong"); }); - var requestSender = await testEnvironment.ConnectClientAsync(); + var requestSender = await testEnvironment.ConnectClient(); using (var rpcClient = new MqttRpcClient(requestSender, new MqttRpcClientOptionsBuilder().Build())) { diff --git a/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs b/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs index 377d1422..a790c543 100644 --- a/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/RoundtripTime_Tests.cs @@ -18,9 +18,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); - var receiverClient = await testEnvironment.ConnectClientAsync(); - var senderClient = await testEnvironment.ConnectClientAsync(); + await testEnvironment.StartServer(); + var receiverClient = await testEnvironment.ConnectClient(); + var senderClient = await testEnvironment.ConnectClient(); await receiverClient.SubscribeAsync("#"); diff --git a/Tests/MQTTnet.Core.Tests/Server/Assigned_Client_ID_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Assigned_Client_ID_Tests.cs new file mode 100644 index 00000000..66365984 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Assigned_Client_ID_Tests.cs @@ -0,0 +1,85 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Client; +using MQTTnet.Client.Options; +using MQTTnet.Formatter; +using MQTTnet.Protocol; +using MQTTnet.Server; + +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_Idtest_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 serverOptions = new MqttServerOptionsBuilder() + .WithConnectionValidator(context => + { + if (string.IsNullOrEmpty(context.ClientId)) + { + context.AssignedClientIdentifier = assignedClientId; + context.ReasonCode = MqttConnectReasonCode.Success; + } + }); + + await testEnvironment.StartServer(serverOptions); + testEnvironment.Server.UseClientConnectedHandler(args => + { + serverConnectedClientId = args.ClientId; + }); + + testEnvironment.Server.UseClientDisconnectedHandler(args => + { + serverDisconnectedClientId = args.ClientId; + disconnectedMre.Set(); + }); + + // Arrange client + var client = testEnvironment.CreateClient(); + client.UseConnectedHandler(args => + { + clientAssignedClientId = args.ConnectResult.AssignedClientIdentifier; + }); + + // 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); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server_Connection_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Connection_Tests.cs similarity index 81% rename from Tests/MQTTnet.Core.Tests/Server_Connection_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Connection_Tests.cs index ebd64d89..cbe9e99b 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Connection_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Connection_Tests.cs @@ -6,19 +6,18 @@ using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Implementations; using MQTTnet.Server; -using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public sealed class Server_Connection_Tests + public sealed class Connection_Tests : BaseTestClass { [TestMethod] public async Task Close_Idle_Connection_On_Connect() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = CreateTestEnvironment()) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); var client = new CrossPlatformSocket(AddressFamily.InterNetwork); await client.ConnectAsync("localhost", testEnvironment.ServerPort, CancellationToken.None); @@ -45,9 +44,9 @@ namespace MQTTnet.Tests [TestMethod] public async Task Send_Garbage() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = CreateTestEnvironment()) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithDefaultCommunicationTimeout(TimeSpan.FromSeconds(1))); + 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. diff --git a/Tests/MQTTnet.Core.Tests/Server_Events_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Events_Tests.cs similarity index 84% rename from Tests/MQTTnet.Core.Tests/Server_Events_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Events_Tests.cs index c90ef825..f74d713e 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Events_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Events_Tests.cs @@ -7,17 +7,17 @@ using MQTTnet.Formatter; using MQTTnet.Protocol; using MQTTnet.Server; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public sealed class Server_Events_Tests : BaseTestClass + public sealed class Events_Tests : BaseTestClass { [TestMethod] public async Task Fire_Client_Connected_Event() { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); MqttServerClientConnectedEventArgs eventArgs = null; server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => @@ -25,14 +25,14 @@ namespace MQTTnet.Tests eventArgs = e; }); - await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser")); + await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event))); - Assert.IsTrue(eventArgs.Endpoint.Contains("::1")); + Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1")); Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion); Assert.AreEqual("TheUser", eventArgs.UserName); } @@ -43,7 +43,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); MqttServerClientDisconnectedEventArgs eventArgs = null; server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(e => @@ -51,15 +51,15 @@ namespace MQTTnet.Tests eventArgs = e; }); - var client = await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser")); + var client = await testEnvironment.ConnectClient(o => o.WithCredentials("TheUser")); await client.DisconnectAsync(); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event))); - Assert.IsTrue(eventArgs.Endpoint.Contains("::1")); + Assert.IsTrue(eventArgs.Endpoint.Contains("127.0.0.1")); Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType); } } @@ -69,7 +69,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); MqttServerClientSubscribedTopicEventArgs eventArgs = null; server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedTopicHandlerDelegate(e => @@ -77,10 +77,10 @@ namespace MQTTnet.Tests eventArgs = e; }); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); @@ -95,7 +95,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); MqttServerClientUnsubscribedTopicEventArgs eventArgs = null; server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(e => @@ -103,10 +103,10 @@ namespace MQTTnet.Tests eventArgs = e; }); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); await client.UnsubscribeAsync("The/Topic"); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); @@ -120,7 +120,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); MqttApplicationMessageReceivedEventArgs eventArgs = null; server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e => @@ -128,10 +128,10 @@ namespace MQTTnet.Tests eventArgs = e; }); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); await client.PublishAsync("The_Topic", "The_Payload"); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); @@ -156,7 +156,7 @@ namespace MQTTnet.Tests await server.StartAsync(new MqttServerOptionsBuilder().Build()); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); } @@ -167,7 +167,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = CreateTestEnvironment()) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); EventArgs eventArgs = null; server.StoppedHandler = new MqttServerStoppedHandlerDelegate(e => @@ -177,7 +177,7 @@ namespace MQTTnet.Tests await server.StopAsync(); - await LongDelay(); + await LongTestDelay(); Assert.IsNotNull(eventArgs); } diff --git a/Tests/MQTTnet.Core.Tests/Server_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/General.cs similarity index 73% rename from Tests/MQTTnet.Core.Tests/Server_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/General.cs index b6a5e71d..f39b5215 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/General.cs @@ -1,3 +1,9 @@ +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.Adapter; using MQTTnet.Client; @@ -6,23 +12,15 @@ using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Options; using MQTTnet.Client.Receiving; using MQTTnet.Client.Subscribing; -using MQTTnet.Exceptions; +using MQTTnet.Formatter; using MQTTnet.Protocol; using MQTTnet.Server; using MQTTnet.Tests.Mockups; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MQTTnet.Diagnostics; -using MQTTnet.Formatter; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public sealed class Server_Tests + public sealed class General_Tests { public TestContext TestContext { get; set; } @@ -34,7 +32,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var client = testEnvironment.CreateClient(); @@ -50,37 +48,12 @@ namespace MQTTnet.Tests } } - [TestMethod] - public async Task Use_Username_Null_Password_Empty() - { - string username = null; - string password = string.Empty; - - using (var testEnvironment = new TestEnvironment()) - { - testEnvironment.IgnoreClientLogErrors = true; - - await testEnvironment.StartServerAsync(); - - var client = testEnvironment.CreateClient(); - - var clientOptions = new MqttClientOptionsBuilder() - .WithTcpServer("localhost", testEnvironment.ServerPort) - .WithCredentials(username, password) - .Build(); - - var ex = await Assert.ThrowsExceptionAsync(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); - } - } - [TestMethod] public async Task Use_Empty_Client_ID() { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var client = testEnvironment.CreateClient(); @@ -137,7 +110,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var client = testEnvironment.CreateClient(); var connectResult = await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).WithCleanSession().Build()); @@ -153,17 +126,17 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").Build(); var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + var c2 = await testEnvironment.ConnectClient(clientOptions); await c2.DisconnectAsync().ConfigureAwait(false); await Task.Delay(1000); @@ -171,7 +144,7 @@ namespace MQTTnet.Tests Assert.AreEqual(0, receivedMessagesCount); } } - + [TestMethod] public async Task Will_Message_Do_Not_Send_On_Takeover() { @@ -179,25 +152,25 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); - + await testEnvironment.StartServer(); + // C1 will receive the last will! - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); // C2 has the last will defined. var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").Build(); - + var clientOptions = new MqttClientOptionsBuilder() .WithWillMessage(willMessage) .WithClientId("WillOwner"); - - var c2 = await testEnvironment.ConnectClientAsync(clientOptions); - + + var c2 = await testEnvironment.ConnectClient(clientOptions); + // C3 will do the connection takeover. - var c3 = await testEnvironment.ConnectClientAsync(clientOptions); - + var c3 = await testEnvironment.ConnectClient(clientOptions); + await Task.Delay(1000); Assert.AreEqual(0, receivedMessagesCount); @@ -211,17 +184,17 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("#").Build()); - var c2 = await testEnvironment.ConnectClientAsync(clientOptions); + var c2 = await testEnvironment.ConnectClient(clientOptions); c2.Dispose(); // Dispose will not send a DISCONNECT pattern first so the will message must be sent. await Task.Delay(1000); @@ -235,7 +208,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithSubscriptionInterceptor( + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithSubscriptionInterceptor( c => { // Set the topic to "a" regards what the client wants to subscribe. @@ -245,7 +218,7 @@ namespace MQTTnet.Tests var topicAReceived = false; var topicBReceived = false; - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); client.UseApplicationMessageReceivedHandler(c => { if (c.ApplicationMessage.Topic == "a") @@ -276,12 +249,12 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c1")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c1")); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); - var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("c2")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("c2")); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); await c2.PublishAsync(message); @@ -290,7 +263,7 @@ namespace MQTTnet.Tests Assert.AreEqual(0, receivedMessagesCount); var subscribeEventCalled = false; - server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedHandlerDelegate(e => + server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedTopicHandlerDelegate(e => { subscribeEventCalled = e.TopicFilter.Topic == "a" && e.ClientId == c1.Options.ClientId; }); @@ -330,9 +303,9 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder() .WithTopicFilter("a") @@ -340,7 +313,7 @@ namespace MQTTnet.Tests .WithTopicFilter("c") .Build()); - var c2 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClient(); await c2.PublishAsync("a"); await Task.Delay(100); @@ -363,9 +336,9 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); var optionsBuilder = new MqttClientSubscribeOptionsBuilder(); @@ -376,7 +349,7 @@ namespace MQTTnet.Tests await c1.SubscribeAsync(optionsBuilder.Build()).ConfigureAwait(false); - var c2 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClient(); var messageBuilder = new MqttApplicationMessageBuilder(); for (var i = 0; i < 500; i++) @@ -399,9 +372,9 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); for (var i = 0; i < 500; i++) @@ -414,7 +387,7 @@ namespace MQTTnet.Tests await Task.Delay(10); } - var c2 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClient(); var messageBuilder = new MqttApplicationMessageBuilder(); for (var i = 0; i < 500; i++) @@ -439,9 +412,9 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttClientSubscribeOptionsBuilder() .WithTopicFilter("a") @@ -455,7 +428,7 @@ namespace MQTTnet.Tests .WithTopicFilter("c") .Build()); - var c2 = await testEnvironment.ConnectClientAsync(); + var c2 = await testEnvironment.ConnectClient(); await c2.PublishAsync("a"); await Task.Delay(100); @@ -476,11 +449,11 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); var receivedMessagesCount = 0; - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); client.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); var message = new MqttApplicationMessageBuilder().WithTopic("a").WithAtLeastOnceQoS().Build(); @@ -501,21 +474,15 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(); - var c2 = await testEnvironment.ConnectClientAsync(); - var c3 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); + var c2 = await testEnvironment.ConnectClient(); + var c3 = await testEnvironment.ConnectClient(); - c2.UseApplicationMessageReceivedHandler(c => - { - Interlocked.Increment(ref receivedMessagesCount); - }); + c2.UseApplicationMessageReceivedHandler(c => { Interlocked.Increment(ref receivedMessagesCount); }); - c3.UseApplicationMessageReceivedHandler(c => - { - Interlocked.Increment(ref receivedMessagesCount); - }); + c3.UseApplicationMessageReceivedHandler(c => { Interlocked.Increment(ref receivedMessagesCount); }); await c2.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); await c3.SubscribeAsync(new MqttTopicFilter { Topic = "a", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }).ConfigureAwait(false); @@ -538,23 +505,20 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); var options = new MqttClientOptionsBuilder() .WithCleanSession(false) .WithProtocolVersion(MqttProtocolVersion.V500) // Disconnect reason is only available in MQTT 5+ .WithClientId("a"); - var client1 = await testEnvironment.ConnectClientAsync(options); + var client1 = await testEnvironment.ConnectClient(options); await Task.Delay(500); MqttClientDisconnectReason disconnectReason = MqttClientDisconnectReason.NormalDisconnection; - client1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(c => - { - disconnectReason = c.Reason; - }); + client1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(c => { disconnectReason = c.Reason; }); - var client2 = await testEnvironment.ConnectClientAsync(options); + var client2 = await testEnvironment.ConnectClient(options); await Task.Delay(500); Assert.IsFalse(client1.IsConnected); @@ -569,15 +533,12 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); var receivedMessages = new List(); - client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => - { - await client.PublishAsync("Connected"); - }); + client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e => { await client.PublishAsync("Connected"); }); client.UseApplicationMessageReceivedHandler(c => { @@ -602,15 +563,15 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - + var server = await testEnvironment.StartServer(); + server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(async e => { // Every client will automatically subscribe to this topic. await server.SubscribeAsync(e.ClientId, "topic1"); }); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); var receivedMessages = new List(); client.UseApplicationMessageReceivedHandler(c => @@ -638,11 +599,11 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); var disconnectCalled = 0; - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); c1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(e => disconnectCalled++); await Task.Delay(100); @@ -660,7 +621,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); var clientConnectedCalled = 0; var clientDisconnectedCalled = 0; @@ -668,8 +629,10 @@ namespace MQTTnet.Tests server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(_ => Interlocked.Increment(ref clientConnectedCalled)); server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(_ => Interlocked.Increment(ref clientDisconnectedCalled)); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); + await Task.Delay(1000); + Assert.AreEqual(1, clientConnectedCalled); Assert.AreEqual(0, clientDisconnectedCalled); @@ -691,10 +654,10 @@ namespace MQTTnet.Tests { bool clientWasConnected; - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); try { - var client = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder()); + var client = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder()); clientWasConnected = true; @@ -720,7 +683,7 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); var tasks = new List(); for (var i = 0; i < ClientCount; i++) @@ -732,7 +695,7 @@ namespace MQTTnet.Tests { try { - using (var client = await testEnvironment2.ConnectClientAsync()) + using (var client = await testEnvironment2.ConnectClient()) { // Clear retained message. await client.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("r" + i2) @@ -769,164 +732,17 @@ namespace MQTTnet.Tests } } - [TestMethod] - public async Task Retained_Messages_Flow() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var retainedMessage = new MqttApplicationMessageBuilder().WithTopic("r").WithPayload("r").WithRetainFlag().Build(); - - await testEnvironment.StartServerAsync(); - var c1 = await testEnvironment.ConnectClientAsync(); - - var receivedMessages = 0; - - var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseApplicationMessageReceivedHandler(c => - { - Interlocked.Increment(ref receivedMessages); - }); - - await c1.PublishAsync(retainedMessage); - await c1.DisconnectAsync(); - await Task.Delay(500); - - for (var i = 0; i < 5; i++) - { - await c2.UnsubscribeAsync("r"); - await Task.Delay(100); - Assert.AreEqual(i, receivedMessages); - - await c2.SubscribeAsync("r"); - await Task.Delay(100); - Assert.AreEqual(i + 1, receivedMessages); - } - - await c2.DisconnectAsync(); - } - } - - [TestMethod] - public async Task Receive_No_Retained_Message_After_Subscribe() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServerAsync(); - - var c1 = await testEnvironment.ConnectClientAsync(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.DisconnectAsync(); - - var receivedMessagesCount = 0; - - var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); - await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained_other").Build()); - - await Task.Delay(500); - - Assert.AreEqual(0, receivedMessagesCount); - } - } - - [TestMethod] - public async Task Receive_Retained_Message_After_Subscribe() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - await testEnvironment.StartServerAsync(); - - var c1 = await testEnvironment.ConnectClientAsync(); - await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); - await c1.DisconnectAsync(); - - var receivedMessages = new List(); - - var c2 = await testEnvironment.ConnectClientAsync(); - c2.UseApplicationMessageReceivedHandler(c => - { - lock (receivedMessages) - { - receivedMessages.Add(c.ApplicationMessage); - } - }); - - await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("retained").Build()); - - await Task.Delay(500); - - Assert.AreEqual(1, receivedMessages.Count); - Assert.IsTrue(receivedMessages.First().Retain); - } - } - - [TestMethod] - public async Task Clear_Retained_Message_With_Empty_Payload() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var receivedMessagesCount = 0; - - await testEnvironment.StartServerAsync(); - - var c1 = await testEnvironment.ConnectClientAsync(); - - 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.ConnectClientAsync(); - - c2.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); - - await Task.Delay(200); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await Task.Delay(500); - - Assert.AreEqual(0, receivedMessagesCount); - } - } - - [TestMethod] - public async Task Clear_Retained_Message_With_Null_Payload() - { - using (var testEnvironment = new TestEnvironment(TestContext)) - { - var receivedMessagesCount = 0; - - await testEnvironment.StartServerAsync(); - - var c1 = await testEnvironment.ConnectClientAsync(); - - 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.ConnectClientAsync(); - - c2.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); - - await Task.Delay(200); - await c2.SubscribeAsync(new MqttTopicFilter { Topic = "retained", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await Task.Delay(500); - - Assert.AreEqual(0, receivedMessagesCount); - } - } - [TestMethod] public async Task Intercept_Application_Message() { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync( + await testEnvironment.StartServer( new MqttServerOptionsBuilder().WithApplicationMessageInterceptor( c => { c.ApplicationMessage = new MqttApplicationMessage { Topic = "new_topic" }; })); string receivedTopic = null; - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); await c1.SubscribeAsync("#"); c1.UseApplicationMessageReceivedHandler(a => { receivedTopic = a.ApplicationMessage.Topic; }); @@ -944,9 +760,9 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithStorage(serverStorage)); + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithStorage(serverStorage)); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("retained").WithPayload(new byte[3]).WithRetainFlag().Build()); @@ -961,22 +777,19 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); - server.UseClientConnectedHandler(async e => - { - await server.PublishAsync("/test/1", "true", MqttQualityOfServiceLevel.ExactlyOnce, false); - }); + var server = await testEnvironment.StartServer(); + server.UseClientConnectedHandler(async e => { await server.PublishAsync("/test/1", "true", MqttQualityOfServiceLevel.ExactlyOnce, false); }); string receivedTopic = null; - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); c1.UseApplicationMessageReceivedHandler(e => { receivedTopic = e.ApplicationMessage.Topic; }); await c1.SubscribeAsync("#"); - await testEnvironment.ConnectClientAsync(); - await testEnvironment.ConnectClientAsync(); - await testEnvironment.ConnectClientAsync(); - await testEnvironment.ConnectClientAsync(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); + await testEnvironment.ConnectClient(); await Task.Delay(500); @@ -994,17 +807,14 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithApplicationMessageInterceptor(Interceptor)); + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithApplicationMessageInterceptor(Interceptor)); - var c1 = await testEnvironment.ConnectClientAsync(); - var c2 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); + var c2 = await testEnvironment.ConnectClient(); await c2.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("test").Build()); var isIntercepted = false; - c2.UseApplicationMessageReceivedHandler(c => - { - isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(c.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; - }); + c2.UseApplicationMessageReceivedHandler(c => { isIntercepted = string.Compare("extended", Encoding.UTF8.GetString(c.ApplicationMessage.Payload), StringComparison.Ordinal) == 0; }); await c1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("test").Build()); await c1.DisconnectAsync(); @@ -1040,17 +850,14 @@ namespace MQTTnet.Tests byte[] receivedBody = null; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client1 = await testEnvironment.ConnectClientAsync(); - client1.UseApplicationMessageReceivedHandler(c => - { - receivedBody = c.ApplicationMessage.Payload; - }); + var client1 = await testEnvironment.ConnectClient(); + client1.UseApplicationMessageReceivedHandler(c => { receivedBody = c.ApplicationMessage.Payload; }); await client1.SubscribeAsync("string"); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); await client2.PublishAsync("string", longBody); await Task.Delay(TimeSpan.FromSeconds(5)); @@ -1062,18 +869,15 @@ namespace MQTTnet.Tests [TestMethod] public async Task Deny_Connection() { - var serverOptions = new MqttServerOptionsBuilder().WithConnectionValidator(context => - { - context.ReasonCode = MqttConnectReasonCode.NotAuthorized; - }); + var serverOptions = new MqttServerOptionsBuilder().WithConnectionValidator(context => { context.ReasonCode = MqttConnectReasonCode.NotAuthorized; }); using (var testEnvironment = new TestEnvironment(TestContext)) { testEnvironment.IgnoreClientLogErrors = true; - await testEnvironment.StartServerAsync(serverOptions); + await testEnvironment.StartServer(serverOptions); - var connectingFailedException = await Assert.ThrowsExceptionAsync(() => testEnvironment.ConnectClientAsync()); + var connectingFailedException = await Assert.ThrowsExceptionAsync(() => testEnvironment.ConnectClient()); Assert.AreEqual(MqttClientConnectResultCode.NotAuthorized, connectingFailedException.ResultCode); } } @@ -1103,7 +907,7 @@ namespace MQTTnet.Tests _connected = new Dictionary(); var options = new MqttServerOptionsBuilder(); options.WithConnectionValidator(e => ConnectionValidationHandler(e)); - var server = await testEnvironment.StartServerAsync(options); + var server = await testEnvironment.StartServer(options); var events = new List(); @@ -1127,7 +931,7 @@ namespace MQTTnet.Tests .WithClientId("same_id"); // c - var c1 = await testEnvironment.ConnectClientAsync(clientOptions); + var c1 = await testEnvironment.ConnectClient(clientOptions); c1.UseDisconnectedHandler(_ => { @@ -1144,7 +948,6 @@ namespace MQTTnet.Tests { events.Add("r"); } - }); c1.SubscribeAsync("topic").Wait(); @@ -1160,7 +963,7 @@ namespace MQTTnet.Tests try { - await testEnvironment.ConnectClientAsync(clientOptions); + await testEnvironment.ConnectClient(clientOptions); Assert.Fail("same id connection is expected to fail"); } catch @@ -1187,7 +990,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment()) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); var events = new List(); @@ -1211,7 +1014,7 @@ namespace MQTTnet.Tests .WithClientId(Guid.NewGuid().ToString()); // c - var c1 = await testEnvironment.ConnectClientAsync(clientOptionsBuilder); + var c1 = await testEnvironment.ConnectClient(clientOptionsBuilder); await Task.Delay(500); @@ -1220,7 +1023,7 @@ namespace MQTTnet.Tests // dc // Connect client with same client ID. Should disconnect existing client. - var c2 = await testEnvironment.ConnectClientAsync(clientOptionsBuilder); + var c2 = await testEnvironment.ConnectClient(clientOptionsBuilder); await Task.Delay(500); @@ -1272,10 +1075,10 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder()); var clientOptions = new MqttClientOptionsBuilder(); - var c1 = await testEnvironment.ConnectClientAsync(clientOptions); + var c1 = await testEnvironment.ConnectClient(clientOptions); await Task.Delay(500); Assert.AreEqual(1, (await server.GetClientStatusAsync()).Count); @@ -1293,14 +1096,14 @@ namespace MQTTnet.Tests { testEnvironment.IgnoreClientLogErrors = true; - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); - await testEnvironment.ConnectClientAsync(); + await testEnvironment.ConnectClient(); await server.StopAsync(); try { - await testEnvironment.ConnectClientAsync(); + await testEnvironment.ConnectClient(); Assert.Fail("Connecting should fail."); } catch (Exception) @@ -1308,7 +1111,7 @@ namespace MQTTnet.Tests } await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultEndpointPort(testEnvironment.ServerPort).Build()); - await testEnvironment.ConnectClientAsync(); + await testEnvironment.ConnectClient(); } } @@ -1317,7 +1120,7 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithSubscriptionInterceptor(c => + await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithSubscriptionInterceptor(c => { // This should lead to no subscriptions for "n" at all. So also no sending of retained messages. if (c.TopicFilter.Topic == "n") @@ -1327,7 +1130,7 @@ namespace MQTTnet.Tests })); // Prepare some retained messages. - var client1 = await testEnvironment.ConnectClientAsync(); + var client1 = await testEnvironment.ConnectClient(); await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("y").WithPayload("x").WithRetainFlag().Build()); await client1.PublishAsync(new MqttApplicationMessageBuilder().WithTopic("n").WithPayload("x").WithRetainFlag().Build()); await client1.DisconnectAsync(); @@ -1336,7 +1139,7 @@ namespace MQTTnet.Tests // Subscribe to all retained message types. // It is important to do this in a range of filters to ensure that a subscription is not "hidden". - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); var buffer = new StringBuilder(); @@ -1361,10 +1164,10 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); // Create the session including the subscription. - var client1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("a")); + var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a")); await client1.SubscribeAsync("x"); await client1.DisconnectAsync(); await Task.Delay(500); @@ -1372,7 +1175,7 @@ namespace MQTTnet.Tests var clientStatus = await server.GetClientStatusAsync(); Assert.AreEqual(0, clientStatus.Count); - var client2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("b")); + var client2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("b")); await client2.PublishAsync("x", "1"); await client2.PublishAsync("x", "2"); await client2.PublishAsync("x", "3"); @@ -1402,13 +1205,13 @@ namespace MQTTnet.Tests { var receivedMessagesCount = 0; - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("receiver")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("receiver")); c1.UseApplicationMessageReceivedHandler(c => Interlocked.Increment(ref receivedMessagesCount)); await c1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topicFilter).WithQualityOfServiceLevel(filterQualityOfServiceLevel).Build()); - var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("sender")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("sender")); await c2.PublishAsync(new MqttApplicationMessageBuilder().WithTopic(topic).WithPayload(new byte[0]).WithQualityOfServiceLevel(qualityOfServiceLevel).Build()); await c2.DisconnectAsync().ConfigureAwait(false); @@ -1428,14 +1231,11 @@ namespace MQTTnet.Tests var undeliverd = string.Empty; var options = new MqttServerOptionsBuilder().WithUndeliveredMessageInterceptor( - context => - { - undeliverd = context.ApplicationMessage.Topic; - }); + context => { undeliverd = context.ApplicationMessage.Topic; }); - await testEnvironment.StartServerAsync(options); + await testEnvironment.StartServer(options); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); await client.SubscribeAsync("b"); @@ -1452,13 +1252,13 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder() + await testEnvironment.StartServer(new MqttServerOptionsBuilder() .WithClientMessageQueueInterceptor(c => c.ApplicationMessage.Topic = "a")); var topicAReceived = false; var topicBReceived = false; - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); client.UseApplicationMessageReceivedHandler(c => { if (c.ApplicationMessage.Topic == "a") @@ -1490,15 +1290,12 @@ namespace MQTTnet.Tests using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder() + await testEnvironment.StartServer(new MqttServerOptionsBuilder() .WithClientMessageQueueInterceptor(c => { })); // Interceptor does nothing but has to be present. bool receivedMessage = false; - var client = await testEnvironment.ConnectClientAsync(); - client.UseApplicationMessageReceivedHandler(c => - { - receivedMessage = true; - }); + var client = await testEnvironment.ConnectClient(); + client.UseApplicationMessageReceivedHandler(c => { receivedMessage = true; }); await client.SubscribeAsync(topic, MqttQualityOfServiceLevel.AtLeastOnce); @@ -1510,4 +1307,4 @@ namespace MQTTnet.Tests } } } -} +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server_KeepAlive_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Keep_Alive_Tests.cs similarity index 89% rename from Tests/MQTTnet.Core.Tests/Server_KeepAlive_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Keep_Alive_Tests.cs index decbb763..dd98509d 100644 --- a/Tests/MQTTnet.Core.Tests/Server_KeepAlive_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Keep_Alive_Tests.cs @@ -5,21 +5,20 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Formatter; using MQTTnet.Packets; using MQTTnet.Protocol; -using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public sealed class Server_KeepAlive_Tests + public sealed class KeepAlive_Tests : BaseTestClass { [TestMethod] public async Task Disconnect_Client_DueTo_KeepAlive() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = CreateTestEnvironment()) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var client = await testEnvironment.ConnectLowLevelClientAsync(o => o + var client = await testEnvironment.ConnectLowLevelClient(o => o .WithCommunicationTimeout(TimeSpan.FromSeconds(1)) .WithCommunicationTimeout(TimeSpan.Zero) .WithProtocolVersion(MqttProtocolVersion.V500)).ConfigureAwait(false); @@ -27,7 +26,7 @@ namespace MQTTnet.Tests await client.SendAsync(new MqttConnectPacket { CleanSession = true, - ClientId = "abc", + ClientId = "Disconnect_Client_DueTo_KeepAlive", KeepAlivePeriod = 1 }, CancellationToken.None).ConfigureAwait(false); diff --git a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManager_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/MqttSubscriptionsManager_Tests.cs similarity index 74% rename from Tests/MQTTnet.Core.Tests/MqttSubscriptionsManager_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/MqttSubscriptionsManager_Tests.cs index 3d38ca74..4ef18b4d 100644 --- a/Tests/MQTTnet.Core.Tests/MqttSubscriptionsManager_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/MqttSubscriptionsManager_Tests.cs @@ -1,13 +1,13 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Packets; using MQTTnet.Protocol; using MQTTnet.Server; -using MQTTnet.Tests.Mockups; -using System.Collections.Concurrent; -using System.Threading.Tasks; using MQTTnet.Server.Internal; +using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] public class MqttSubscriptionsManager_Tests @@ -17,14 +17,14 @@ namespace MQTTnet.Tests { var s = CreateSession(); - var sm = new MqttClientSubscriptionsManager(s, new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions()); + var sm = new MqttClientSubscriptionsManager(s, new MqttServerOptions(), new MqttServerEventDispatcher(new TestLogger()), new MqttRetainedMessagesManager()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()); - await sm.SubscribeAsync(sp, new MqttConnectPacket()); + await sm.Subscribe(sp); - var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce); + var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, ""); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); } @@ -34,14 +34,14 @@ namespace MQTTnet.Tests { var s = CreateSession(); - var sm = new MqttClientSubscriptionsManager(s, new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions()); + var sm = new MqttClientSubscriptionsManager(s, new MqttServerOptions(), new MqttServerEventDispatcher(new TestLogger()), new MqttRetainedMessagesManager()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); - await sm.SubscribeAsync(sp, new MqttConnectPacket()); + await sm.Subscribe(sp); - var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); + var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtMostOnce); } @@ -51,15 +51,15 @@ namespace MQTTnet.Tests { var s = CreateSession(); - var sm = new MqttClientSubscriptionsManager(s, new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions()); + var sm = new MqttClientSubscriptionsManager(s, new MqttServerOptions(), new MqttServerEventDispatcher(new TestLogger()), new MqttRetainedMessagesManager()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new MqttTopicFilter { Topic = "#", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtMostOnce }); sp.TopicFilters.Add(new MqttTopicFilter { Topic = "A/B/C", QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce }); - await sm.SubscribeAsync(sp, new MqttConnectPacket()); + await sm.Subscribe(sp); - var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce); + var result = sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.ExactlyOnce, ""); Assert.IsTrue(result.IsSubscribed); Assert.AreEqual(result.QualityOfServiceLevel, MqttQualityOfServiceLevel.AtLeastOnce); } @@ -69,14 +69,14 @@ namespace MQTTnet.Tests { var s = CreateSession(); - var sm = new MqttClientSubscriptionsManager(s, new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions()); + var sm = new MqttClientSubscriptionsManager(s, new MqttServerOptions(), new MqttServerEventDispatcher(new TestLogger()), new MqttRetainedMessagesManager()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()); - await sm.SubscribeAsync(sp, new MqttConnectPacket()); + await sm.Subscribe(sp); - Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); + Assert.IsFalse(sm.CheckSubscriptions("A/B/X", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); } [TestMethod] @@ -84,20 +84,20 @@ namespace MQTTnet.Tests { var s = CreateSession(); - var sm = new MqttClientSubscriptionsManager(s, new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions()); + var sm = new MqttClientSubscriptionsManager(s, new MqttServerOptions(), new MqttServerEventDispatcher(new TestLogger()), new MqttRetainedMessagesManager()); var sp = new MqttSubscribePacket(); sp.TopicFilters.Add(new MqttTopicFilterBuilder().WithTopic("A/B/C").Build()); - await sm.SubscribeAsync(sp, new MqttConnectPacket()); + await sm.Subscribe(sp); - Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); + Assert.IsTrue(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); var up = new MqttUnsubscribePacket(); up.TopicFilters.Add("A/B/C"); - await sm.UnsubscribeAsync(up); + await sm.Unsubscribe(up); - Assert.IsFalse(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce).IsSubscribed); + Assert.IsFalse(sm.CheckSubscriptions("A/B/C", MqttQualityOfServiceLevel.AtMostOnce, "").IsSubscribed); } MqttClientSession CreateSession() @@ -107,8 +107,7 @@ namespace MQTTnet.Tests new ConcurrentDictionary(), new MqttServerEventDispatcher(new TestLogger()), new MqttServerOptions(), - new MqttRetainedMessagesManager(), - new TestLogger()); + new MqttRetainedMessagesManager()); } } } diff --git a/Tests/MQTTnet.Core.Tests/Server/No_Local_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/No_Local_Tests.cs new file mode 100644 index 00000000..04f4e28d --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/No_Local_Tests.cs @@ -0,0 +1,47 @@ +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.PublishAsync("Topic", "Payload", true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterPublish); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server/Retain_As_Published_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Retain_As_Published_Tests.cs new file mode 100644 index 00000000..f65557f0 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Retain_As_Published_Tests.cs @@ -0,0 +1,44 @@ +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.PublishAsync("Topic", "Payload", true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(1); + Assert.AreEqual(retainAsPublished, applicationMessageHandler.ReceivedApplicationMessages[0].Retain); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server/Retain_Handling_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Retain_Handling_Tests.cs new file mode 100644 index 00000000..98827ec4 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Retain_Handling_Tests.cs @@ -0,0 +1,66 @@ +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.PublishAsync("Topic", "Payload", 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.PublishAsync("Topic", "Payload", true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondPublish); + + await client2.SubscribeAsync(topicFilter); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(expectedCountAfterSecondSubscribe); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server/Retained_Messages_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Retained_Messages_Tests.cs new file mode 100644 index 00000000..d9ea9d2b --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Retained_Messages_Tests.cs @@ -0,0 +1,171 @@ +using System.Linq; +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 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.ReceivedApplicationMessages.First().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); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server/Security_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Security_Tests.cs new file mode 100644 index 00000000..3cddcf12 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Security_Tests.cs @@ -0,0 +1,90 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Adapter; +using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Exceptions; +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(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); + } + } + + [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"); + } + + async Task TestCredentials(string userName, string password) + { + using (var testEnvironment = CreateTestEnvironment()) + { + testEnvironment.IgnoreClientLogErrors = true; + + await testEnvironment.StartServer(testEnvironment.Factory.CreateServerOptionsBuilder() + .WithConnectionValidator(c => + { + if (c.Username != "UserName1") + { + c.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + } + + if (c.Password != "Password1") + { + c.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword; + } + })); + + var client = testEnvironment.CreateClient(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("127.0.0.1", testEnvironment.ServerPort) + .WithCredentials(userName, password) + .Build(); + + var ex = await Assert.ThrowsExceptionAsync(async () => await client.ConnectAsync(clientOptions)); + Assert.AreEqual(ex.Result.ResultCode, MqttClientConnectResultCode.BadUserNameOrPassword); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server/Server_Reference_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Server_Reference_Tests.cs new file mode 100644 index 00000000..73b2f113 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Server_Reference_Tests.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MQTTnet.Adapter; +using MQTTnet.Client; +using MQTTnet.Client.Connecting; +using MQTTnet.Client.Options; +using MQTTnet.Formatter; +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; + + await testEnvironment.StartServer(o => + { + o.WithConnectionValidator(v => + { + v.ReasonCode = MqttConnectReasonCode.ServerMoved; + v.ServerReference = "new_server"; + }); + }); + + 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); + } + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Session_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Session_Tests.cs similarity index 91% rename from Tests/MQTTnet.Core.Tests/Session_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Session_Tests.cs index 8ca72ee6..e9000cdc 100644 --- a/Tests/MQTTnet.Core.Tests/Session_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Session_Tests.cs @@ -1,14 +1,14 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; using MQTTnet.Client.Options; using MQTTnet.Client.Subscribing; using MQTTnet.Server; using MQTTnet.Tests.Mockups; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] public class Session_Tests @@ -39,11 +39,11 @@ namespace MQTTnet.Tests context.ApplicationMessage.Payload = Encoding.UTF8.GetBytes(context.SessionItems["default_payload"] as string); }); - await testEnvironment.StartServerAsync(serverOptions); + await testEnvironment.StartServer(serverOptions); string receivedPayload = null; - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); client.UseApplicationMessageReceivedHandler(delegate (MqttApplicationMessageReceivedEventArgs args) { receivedPayload = args.ApplicationMessage.ConvertPayloadToString(); @@ -53,7 +53,7 @@ namespace MQTTnet.Tests Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS0, subscribeResult.Items[0].ResultCode); - var client2 = await testEnvironment.ConnectClientAsync(); + var client2 = await testEnvironment.ConnectClient(); await client2.PublishAsync("x"); await Task.Delay(1000); @@ -75,9 +75,9 @@ namespace MQTTnet.Tests context.SessionItems["default_payload"] = "Hello World"; }); - await testEnvironment.StartServerAsync(serverOptions); + await testEnvironment.StartServer(serverOptions); - var client = await testEnvironment.ConnectClientAsync(); + var client = await testEnvironment.ConnectClient(); var sessionStatus = await testEnvironment.Server.GetSessionStatusAsync(); var session = sessionStatus.First(); @@ -94,7 +94,7 @@ namespace MQTTnet.Tests { testEnvironment.IgnoreClientLogErrors = true; var serverOptions = new MqttServerOptionsBuilder(); - await testEnvironment.StartServerAsync(serverOptions); + await testEnvironment.StartServer(serverOptions); var options = new MqttClientOptionsBuilder().WithClientId("1"); @@ -111,7 +111,7 @@ namespace MQTTnet.Tests { try { - return await testEnvironment.ConnectClientAsync(options); + return await testEnvironment.ConnectClient(options); } catch (System.Exception) { diff --git a/Tests/MQTTnet.Core.Tests/Server/Shared_Subscriptions_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Shared_Subscriptions_Tests.cs new file mode 100644 index 00000000..d510e7a8 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Shared_Subscriptions_Tests.cs @@ -0,0 +1,27 @@ +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); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Status_Tests.cs similarity index 76% rename from Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Status_Tests.cs index fee808dc..c3f9f389 100644 --- a/Tests/MQTTnet.Core.Tests/Server_Status_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Status_Tests.cs @@ -1,26 +1,26 @@ using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Threading.Tasks; -using MQTTnet.Client.Options; -using MQTTnet.Tests.Mockups; +using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Protocol; using MQTTnet.Server; +using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public sealed class Server_Status_Tests : BaseTestClass + 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.StartServerAsync(); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); - var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client2")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2")); await Task.Delay(500); @@ -51,9 +51,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(); + var server = await testEnvironment.StartServer(); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); await Task.Delay(1000); @@ -79,10 +79,10 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client1")); - var c2 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithClientId("client2")); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client1")); + var c2 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("client2")); await c1.DisconnectAsync(); @@ -111,9 +111,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClientAsync(); + var c1 = await testEnvironment.ConnectClient(); for (var i = 1; i < 25; i++) { @@ -132,9 +132,9 @@ namespace MQTTnet.Tests { using (var testEnvironment = new TestEnvironment(TestContext)) { - var server = await testEnvironment.StartServerAsync(new MqttServerOptionsBuilder().WithPersistentSessions()); + var server = await testEnvironment.StartServer(new MqttServerOptionsBuilder().WithPersistentSessions()); - var c1 = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithNoKeepAlive()); + var c1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithNoKeepAlive()); for (var i = 1; i < 25; i++) { diff --git a/Tests/MQTTnet.Core.Tests/Server/Subscription_Identifier_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Subscription_Identifier_Tests.cs new file mode 100644 index 00000000..c69202a0 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Subscription_Identifier_Tests.cs @@ -0,0 +1,89 @@ +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.PublishAsync("Topic", "Payload", true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(1); + + applicationMessageHandler.ReceivedApplicationMessages[0].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.PublishAsync("Topic/A", "Payload", true); + await LongTestDelay(); + + applicationMessageHandler.AssertReceivedCountEquals(1); + + applicationMessageHandler.ReceivedApplicationMessages[0].SubscriptionIdentifiers.Contains(456); + applicationMessageHandler.ReceivedApplicationMessages[0].SubscriptionIdentifiers.Contains(789); + } + } + } +} \ No newline at end of file diff --git a/Tests/MQTTnet.Core.Tests/TopicFilterComparer_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/TopicFilterComparer_Tests.cs similarity index 98% rename from Tests/MQTTnet.Core.Tests/TopicFilterComparer_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/TopicFilterComparer_Tests.cs index 56ebf391..e885ff02 100644 --- a/Tests/MQTTnet.Core.Tests/TopicFilterComparer_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/TopicFilterComparer_Tests.cs @@ -1,8 +1,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using MQTTnet.Server; using MQTTnet.Server.Internal; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] public class TopicFilterComparer_Tests diff --git a/Tests/MQTTnet.Core.Tests/Server_TopicAlias_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Topic_Alias_Tests.cs similarity index 50% rename from Tests/MQTTnet.Core.Tests/Server_TopicAlias_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/Topic_Alias_Tests.cs index e831491f..ba5e6f6c 100644 --- a/Tests/MQTTnet.Core.Tests/Server_TopicAlias_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/Topic_Alias_Tests.cs @@ -3,24 +3,42 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using MQTTnet.Client; +using MQTTnet.Client.Options; using MQTTnet.Formatter; -using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests +namespace MQTTnet.Tests.Server { [TestClass] - public class Server_TopicAlias_Tests + public sealed class Topic_Alias_Tests : BaseTestClass { [TestMethod] - public async Task Publish_After_Client_Connects() + public async Task Server_Reports_Topic_Alias_Supported() { - using (var testEnvironment = new TestEnvironment()) + using (var testEnvironment = CreateTestEnvironment()) { - await testEnvironment.StartServerAsync(); + 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(); - var c1 = await testEnvironment.ConnectClientAsync(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); + var c1 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); c1.UseApplicationMessageReceivedHandler(e => { lock (receivedTopics) @@ -31,7 +49,7 @@ namespace MQTTnet.Tests await c1.SubscribeAsync("#"); - var c2 = await testEnvironment.ConnectClientAsync(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); + var c2 = await testEnvironment.ConnectClient(options => options.WithProtocolVersion(MqttProtocolVersion.V500)); var message = new MqttApplicationMessage { diff --git a/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/User_Properties_Tests.cs similarity index 87% rename from Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs rename to Tests/MQTTnet.Core.Tests/Server/User_Properties_Tests.cs index 41b20994..fc305916 100644 --- a/Tests/MQTTnet.Core.Tests/MQTTv5/Feature_Tests.cs +++ b/Tests/MQTTnet.Core.Tests/Server/User_Properties_Tests.cs @@ -8,7 +8,7 @@ using MQTTnet.Client.Subscribing; using MQTTnet.Formatter; using MQTTnet.Tests.Mockups; -namespace MQTTnet.Tests.MQTTv5 +namespace MQTTnet.Tests.Server { [TestClass] public class Feature_Tests @@ -20,10 +20,10 @@ namespace MQTTnet.Tests.MQTTv5 { using (var testEnvironment = new TestEnvironment(TestContext)) { - await testEnvironment.StartServerAsync(); + await testEnvironment.StartServer(); - var sender = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); - var receiver = await testEnvironment.ConnectClientAsync(new MqttClientOptionsBuilder().WithProtocolVersion(MqttProtocolVersion.V500)); + 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") diff --git a/Tests/MQTTnet.Core.Tests/Server/Wildcard_Subscription_Available_Tests.cs b/Tests/MQTTnet.Core.Tests/Server/Wildcard_Subscription_Available_Tests.cs new file mode 100644 index 00000000..1b2abe10 --- /dev/null +++ b/Tests/MQTTnet.Core.Tests/Server/Wildcard_Subscription_Available_Tests.cs @@ -0,0 +1,43 @@ +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); + } + } + } +} \ No newline at end of file