Improve internal server connection management. (#1232)

This commit is contained in:
Christian
2021-09-08 18:01:56 +02:00
committed by GitHub
parent 9d9077f17c
commit cd8940f9b7
60 changed files with 980 additions and 589 deletions
+4
View File
@@ -13,6 +13,10 @@
<description>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.</description> <description>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.</description>
<releaseNotes> <releaseNotes>
* [ManagedClient] Extended ReconnectAsync (thanks to @nvsnkv, #1202). * [ManagedClient] Extended ReconnectAsync (thanks to @nvsnkv, #1202).
* [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_.
* [MQTTnet.Server] Moved server project to a dedicated GitHub repository. * [MQTTnet.Server] Moved server project to a dedicated GitHub repository.
* [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh). * [MQTTnet, MQTTnet.Extensions.ManagedClient] Fixed bug that allowed invalid subscriptions (Thanks to @marcelwinh).
Git commit: $gitCommit Git commit: $gitCommit
+3 -3
View File
@@ -363,9 +363,9 @@ namespace MQTTnet.Client
await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false); await Task.WhenAll(receiverTask, publishPacketReceiverTask, keepAliveTask).ConfigureAwait(false);
} }
catch (Exception e) catch (Exception innerException)
{ {
_logger.Warning(e, "Error while waiting for internal tasks."); _logger.Warning(innerException, "Error while waiting for internal tasks.");
} }
finally finally
{ {
@@ -439,7 +439,7 @@ namespace MQTTnet.Client
{ {
if (exception is MqttCommunicationTimedOutException) if (exception is MqttCommunicationTimedOutException)
{ {
_logger.Warning(null, "Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name); _logger.Warning("Timeout while waiting for response packet ({0}).", typeof(TResponsePacket).Name);
} }
throw; throw;
@@ -133,7 +133,7 @@ namespace MQTTnet.Client.Options
return this; return this;
} }
public MqttClientOptionsBuilder WithCredentials(string username, string password = null) public MqttClientOptionsBuilder WithCredentials(string username, string password)
{ {
byte[] passwordBuffer = null; byte[] passwordBuffer = null;
@@ -145,7 +145,7 @@ namespace MQTTnet.Client.Options
return WithCredentials(username, passwordBuffer); return WithCredentials(username, passwordBuffer);
} }
public MqttClientOptionsBuilder WithCredentials(string username, byte[] password = null) public MqttClientOptionsBuilder WithCredentials(string username, byte[] password)
{ {
_options.Credentials = new MqttClientCredentials _options.Credentials = new MqttClientCredentials
{ {
@@ -156,6 +156,16 @@ namespace MQTTnet.Client.Options
return this; return this;
} }
public MqttClientOptionsBuilder WithCredentials(string username)
{
_options.Credentials = new MqttClientCredentials
{
Username = username
};
return this;
}
public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials) public MqttClientOptionsBuilder WithCredentials(IMqttClientCredentials credentials)
{ {
_options.Credentials = credentials; _options.Credentials = credentials;
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Client.Receiving namespace MQTTnet.Client.Receiving
{ {
public class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler public sealed class MqttApplicationMessageReceivedHandlerDelegate : IMqttApplicationMessageReceivedHandler
{ {
private readonly Func<MqttApplicationMessageReceivedEventArgs, Task> _handler; readonly Func<MqttApplicationMessageReceivedEventArgs, Task> _handler;
public MqttApplicationMessageReceivedHandlerDelegate(Action<MqttApplicationMessageReceivedEventArgs> handler) public MqttApplicationMessageReceivedHandlerDelegate(Action<MqttApplicationMessageReceivedEventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Client.Receiving
_handler = context => _handler = context =>
{ {
handler(context); handler(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -8,7 +8,6 @@ using MQTTnet.Client.Unsubscribing;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MqttClientSubscribeResult = MQTTnet.Client.Subscribing.MqttClientSubscribeResult;
namespace MQTTnet.Formatter namespace MQTTnet.Formatter
{ {
@@ -22,7 +21,7 @@ namespace MQTTnet.Formatter
MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket); MqttClientPublishResult CreateClientPublishResult(MqttPubRecPacket pubRecPacket, MqttPubCompPacket pubCompPacket);
MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket); Client.Subscribing.MqttClientSubscribeResult CreateClientSubscribeResult(MqttSubscribePacket subscribePacket, MqttSubAckPacket subAckPacket);
MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket); MqttClientUnsubscribeResult CreateClientUnsubscribeResult(MqttUnsubscribePacket unsubscribePacket, MqttUnsubAckPacket unsubAckPacket);
+1
View File
@@ -8,6 +8,7 @@ namespace MQTTnet
{ {
} }
// TODO: Consider using struct instead.
public class MqttTopicFilter public class MqttTopicFilter
{ {
/// <summary> /// <summary>
@@ -3,7 +3,9 @@
public enum MqttRetainHandling public enum MqttRetainHandling
{ {
SendAtSubscribe = 0, SendAtSubscribe = 0,
SendAtSubscribeIfNewSubscriptionOnly = 1, SendAtSubscribeIfNewSubscriptionOnly = 1,
DoNotSendOnSubscribe = 2 DoNotSendOnSubscribe = 2
} }
} }
@@ -1,6 +1,6 @@
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public struct CheckSubscriptionsResult public struct CheckSubscriptionsResult
{ {
@@ -1,94 +1,82 @@
using MQTTnet.Adapter; using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client; using MQTTnet.Client;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.PacketDispatcher; using MQTTnet.PacketDispatcher;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server.Status; using MQTTnet.Server.Status;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttClientConnection : IDisposable public sealed class MqttClientConnection : IDisposable
{ {
readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>(); readonly Dictionary<ushort, string> _topicAlias = new Dictionary<ushort, string>();
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider(); readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher(); readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly CancellationTokenSource _cancellationToken = new CancellationTokenSource();
readonly IMqttRetainedMessagesManager _retainedMessagesManager; readonly IMqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttClientSessionsManager _sessionsManager; readonly MqttClientSessionsManager _sessionsManager;
readonly IMqttChannelAdapter _channelAdapter;
readonly IMqttNetScopedLogger _logger; readonly IMqttNetScopedLogger _logger;
readonly IMqttServerOptions _serverOptions; readonly IMqttServerOptions _serverOptions;
readonly IMqttChannelAdapter _channelAdapter;
readonly MqttConnectionValidatorContext _connectionValidatorContext;
readonly IMqttDataConverter _dataConverter;
readonly string _endpoint; readonly string _endpoint;
readonly DateTime _connectedTimestamp;
volatile Task _packageReceiverTask; CancellationTokenSource _cancellationToken;
DateTime _lastNonKeepAlivePacketReceivedTimestamp;
long _receivedPacketsCount; public MqttClientConnection(
long _sentPacketsCount = 1; // Start with 1 because the CONNECT packet is not counted anywhere. MqttConnectPacket connectPacket,
long _receivedApplicationMessagesCount;
long _sentApplicationMessagesCount;
MqttClientDisconnectReason _disconnectReason;
public MqttClientConnection(MqttConnectPacket connectPacket,
IMqttChannelAdapter channelAdapter, IMqttChannelAdapter channelAdapter,
MqttClientSession session, MqttClientSession session,
MqttConnectionValidatorContext connectionValidatorContext,
IMqttServerOptions serverOptions, IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager, MqttClientSessionsManager sessionsManager,
IMqttRetainedMessagesManager retainedMessagesManager, IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger) IMqttNetLogger logger)
{ {
Session = session ?? throw new ArgumentNullException(nameof(session));
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions)); _serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager)); _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager)); _retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter)); _channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_connectionValidatorContext = connectionValidatorContext ?? throw new ArgumentNullException(nameof(connectionValidatorContext)); _endpoint = channelAdapter.Endpoint;
_dataConverter = _channelAdapter.PacketFormatterAdapter.DataConverter;
_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)); if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection)); _logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
_connectedTimestamp = DateTime.UtcNow;
LastPacketReceivedTimestamp = _connectedTimestamp;
_lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
} }
public MqttClientConnectionStatus Status { get; private set; } = MqttClientConnectionStatus.Initializing;
public MqttConnectPacket ConnectPacket { get; }
public string ClientId => ConnectPacket.ClientId; public string ClientId => ConnectPacket.ClientId;
public bool IsReadingPacket => _channelAdapter.IsReadingPacket; public string Endpoint => _endpoint;
public DateTime LastPacketReceivedTimestamp { get; private set; } public MqttClientConnectionStatistics Statistics { get; } = new MqttClientConnectionStatistics();
public bool IsRunning { get; private set; }
public MqttConnectPacket ConnectPacket { get; }
public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
public MqttClientSession Session { get; } public MqttClientSession Session { get; }
public bool IsTakenOver { get; set; }
public bool IsCleanDisconnect { get; private set; }
public async Task StopAsync(MqttClientDisconnectReason reason) public async Task StopAsync(MqttClientDisconnectReason reason)
{ {
Status = MqttClientConnectionStatus.Finalizing; IsRunning = false;
_disconnectReason = reason;
if (reason == MqttClientDisconnectReason.SessionTakenOver || reason == MqttClientDisconnectReason.KeepAliveTimeout) if (reason == MqttClientDisconnectReason.SessionTakenOver || reason == MqttClientDisconnectReason.KeepAliveTimeout)
{ {
@@ -96,30 +84,10 @@ namespace MQTTnet.Server
// token because the entire connection is closed (disposed) as soon as the cancellation // token because the entire connection is closed (disposed) as soon as the cancellation
// token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive // token is cancelled. To there is no chance that the DISCONNECT packet will ever arrive
// at the client! // at the client!
try await TrySendDisconnectPacket(reason).ConfigureAwait(false);
{
var disconnectOptions = new MqttClientDisconnectOptions
{
ReasonCode = reason,
ReasonString = reason.ToString()
};
var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
{
await _channelAdapter.SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
}
}
catch (Exception exception)
{
_logger.Warning(exception, "Client '{0}': Error while sending DISCONNECT packet after takeover.", ClientId);
}
} }
StopInternal(); StopInternal();
await (_packageReceiverTask ?? PlatformAbstractionLayer.CompletedTask);
} }
public void ResetStatistics() public void ResetStatistics()
@@ -127,24 +95,16 @@ namespace MQTTnet.Server
_channelAdapter.ResetStatistics(); _channelAdapter.ResetStatistics();
} }
public void FillStatus(MqttClientStatus status) public void FillClientStatus(MqttClientStatus clientStatus)
{ {
status.ClientId = ClientId; clientStatus.ClientId = ClientId;
status.Endpoint = _endpoint; clientStatus.Endpoint = _endpoint;
status.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
status.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount); clientStatus.ProtocolVersion = _channelAdapter.PacketFormatterAdapter.ProtocolVersion;
status.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount); clientStatus.BytesSent = _channelAdapter.BytesSent;
clientStatus.BytesReceived = _channelAdapter.BytesReceived;
status.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount); Statistics.FillClientStatus(clientStatus);
status.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
status.ConnectedTimestamp = _connectedTimestamp;
status.LastPacketReceivedTimestamp = LastPacketReceivedTimestamp;
status.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
status.BytesSent = _channelAdapter.BytesSent;
status.BytesReceived = _channelAdapter.BytesReceived;
} }
public void Dispose() public void Dispose()
@@ -152,31 +112,60 @@ namespace MQTTnet.Server
_cancellationToken.Dispose(); _cancellationToken.Dispose();
} }
public Task RunAsync() public async Task RunAsync()
{ {
_packageReceiverTask = RunInternalAsync(_cancellationToken.Token); _logger.Info("Client '{0}': Session started.", ClientId);
return _packageReceiverTask;
Session.WillMessage = ConnectPacket.WillMessage;
using (var cancellationToken = new CancellationTokenSource())
{
_cancellationToken = cancellationToken;
try
{
Task.Run(() => SendPacketsLoop(cancellationToken.Token), cancellationToken.Token).RunInBackground(_logger);
Session.IsCleanSession = false;
IsRunning = true;
await ReceivePackagesLoop(cancellationToken.Token).ConfigureAwait(false);
}
finally
{
IsRunning = false;
cancellationToken.Cancel();
_cancellationToken = null;
}
}
_packetDispatcher.CancelAll();
if (!IsTakenOver && !IsCleanDisconnect && Session.WillMessage != null)
{
_sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
Session.WillMessage = null;
}
_logger.Info("Client '{0}': Connection stopped.", ClientId);
} }
async Task RunInternalAsync(CancellationToken cancellationToken) Task SendPacketAsync(MqttBasePacket packet, CancellationToken cancellationToken)
{
return _channelAdapter.SendPacketAsync(packet, cancellationToken).ContinueWith(task => { Statistics.HandleSentPacket(packet); }, cancellationToken);
}
async Task ReceivePackagesLoop(CancellationToken cancellationToken)
{ {
var disconnectType = MqttClientDisconnectType.NotClean;
try try
{ {
_logger.Info("Client '{0}': Session started.", ClientId); // We do not listen for the cancellation token here because the internal buffer might still
// contain data to be read even if the TCP connection was already dropped. So we rely on an
Session.WillMessage = ConnectPacket.WillMessage; // own exception in the reading loop!
await SendAsync(_channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(_connectionValidatorContext), cancellationToken).ConfigureAwait(false);
Task.Run(() => SendPendingPacketsAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
Session.IsCleanSession = false;
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
Status = MqttClientConnectionStatus.Running;
var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false); var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);
if (packet == null) if (packet == null)
{ {
@@ -184,13 +173,7 @@ namespace MQTTnet.Server
return; return;
} }
Interlocked.Increment(ref _sentPacketsCount); Statistics.HandleReceivedPacket(packet);
LastPacketReceivedTimestamp = DateTime.UtcNow;
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
{
_lastNonKeepAlivePacketReceivedTimestamp = LastPacketReceivedTimestamp;
}
if (packet is MqttPublishPacket publishPacket) if (packet is MqttPublishPacket publishPacket)
{ {
@@ -210,7 +193,8 @@ namespace MQTTnet.Server
} }
else if (packet is MqttPingReqPacket) else if (packet is MqttPingReqPacket)
{ {
await SendAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false); // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
} }
else if (packet is MqttPingRespPacket) else if (packet is MqttPingRespPacket)
{ {
@@ -218,10 +202,7 @@ namespace MQTTnet.Server
} }
else if (packet is MqttDisconnectPacket) else if (packet is MqttDisconnectPacket)
{ {
Session.WillMessage = null; IsCleanDisconnect = true;
disconnectType = MqttClientDisconnectType.Clean;
StopInternal();
return; return;
} }
else else
@@ -246,40 +227,111 @@ namespace MQTTnet.Server
{ {
_logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId); _logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId);
} }
}
}
async Task SendPacketsLoop(CancellationToken cancellationToken)
{
MqttQueuedApplicationMessage queuedApplicationMessage = null;
MqttPublishPacket publishPacket = null;
try
{
while (!cancellationToken.IsCancellationRequested)
{
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
if (queuedApplicationMessage == null)
{
return;
}
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)
{
// The interceptor has decided that the message is not relevant and will be fully ignored.
continue;
}
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
using (var awaiter = _packetDispatcher.AddAwaiter<MqttPubAckPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
using (var awaiter1 = _packetDispatcher.AddAwaiter<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRecPacket = await awaiter1.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);
}
}
_logger.Verbose("Client '{0}': Queued application message sent.", ClientId);
}
}
catch (OperationCanceledException)
{
}
catch (Exception exception)
{
if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", ClientId);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", ClientId);
}
else
{
_logger.Error(exception, "Client '{0}': Sending publish packet failed.", ClientId);
}
if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
if (queuedApplicationMessage != null)
{
queuedApplicationMessage.IsDuplicate = true;
Session.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
}
}
StopInternal(); StopInternal();
} }
finally
{
if (_disconnectReason == MqttClientDisconnectReason.SessionTakenOver)
{
disconnectType = MqttClientDisconnectType.Takeover;
}
if (Session.WillMessage != null)
{
_sessionsManager.DispatchApplicationMessage(Session.WillMessage, this);
Session.WillMessage = null;
}
_packetDispatcher.CancelAll();
_logger.Info("Client '{0}': Connection stopped.", ClientId);
try
{
await _sessionsManager.CleanUpClient(ClientId, _channelAdapter, disconnectType);
}
catch (Exception e)
{
_logger.Error(e, "Client '{0}': Error while cleaning up", ClientId);
}
}
} }
void StopInternal() void StopInternal()
{ {
_cancellationToken.Cancel(); _cancellationToken?.Cancel();
} }
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters) async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
@@ -294,7 +346,7 @@ namespace MQTTnet.Server
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken) Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{ {
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubCompPacket, cancellationToken); return SendPacketAsync(pubCompPacket, cancellationToken);
} }
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken) async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
@@ -302,7 +354,7 @@ namespace MQTTnet.Server
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false); var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult); var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
await SendAsync(subAckPacket, cancellationToken).ConfigureAwait(false); await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
if (subscribeResult.CloseConnection) if (subscribeResult.CloseConnection)
{ {
@@ -318,38 +370,36 @@ namespace MQTTnet.Server
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false); var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes); var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
await SendAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false); await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
} }
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken) Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{ {
Interlocked.Increment(ref _sentApplicationMessagesCount);
HandleTopicAlias(publishPacket); HandleTopicAlias(publishPacket);
var applicationMessage = _dataConverter.CreateApplicationMessage(publishPacket); var applicationMessage = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateApplicationMessage(publishPacket);
_sessionsManager.DispatchApplicationMessage(applicationMessage, this); _sessionsManager.DispatchApplicationMessage(applicationMessage, this);
switch (publishPacket.QualityOfServiceLevel) switch (publishPacket.QualityOfServiceLevel)
{ {
case MqttQualityOfServiceLevel.AtMostOnce: case MqttQualityOfServiceLevel.AtMostOnce:
{ {
return PlatformAbstractionLayer.CompletedTask; return PlatformAbstractionLayer.CompletedTask;
} }
case MqttQualityOfServiceLevel.AtLeastOnce: case MqttQualityOfServiceLevel.AtLeastOnce:
{ {
var pubAckPacket = _dataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubAckPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubAckPacket, cancellationToken); return SendPacketAsync(pubAckPacket, cancellationToken);
} }
case MqttQualityOfServiceLevel.ExactlyOnce: case MqttQualityOfServiceLevel.ExactlyOnce:
{ {
var pubRecPacket = _dataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success); var pubRecPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRecPacket(publishPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendAsync(pubRecPacket, cancellationToken); return SendPacketAsync(pubRecPacket, cancellationToken);
} }
default: default:
{ {
throw new MqttCommunicationException("Received a not supported QoS level."); throw new MqttCommunicationException("Received a not supported QoS level.");
} }
} }
} }
@@ -376,134 +426,64 @@ namespace MQTTnet.Server
} }
else else
{ {
} }
} }
} }
} }
async Task SendPendingPacketsAsync(CancellationToken cancellationToken) async Task TrySendDisconnectPacket(MqttClientDisconnectReason reason)
{ {
MqttQueuedApplicationMessage queuedApplicationMessage = null;
MqttPublishPacket publishPacket = null;
try try
{ {
while (!cancellationToken.IsCancellationRequested) var disconnectOptions = new MqttClientDisconnectOptions
{ {
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false); ReasonCode = reason,
if (queuedApplicationMessage == null) ReasonString = reason.ToString()
{ };
return;
}
if (cancellationToken.IsCancellationRequested) var disconnectPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateDisconnectPacket(disconnectOptions);
{
return;
}
publishPacket = _dataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage); using (var timeout = new CancellationTokenSource(_serverOptions.DefaultCommunicationTimeout))
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel; {
await SendPacketAsync(disconnectPacket, timeout.Token).ConfigureAwait(false);
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
if (_serverOptions.ClientMessageQueueInterceptor != null)
{
var context = new MqttClientMessageQueueInterceptorContext(
queuedApplicationMessage.SenderClientId,
ClientId,
queuedApplicationMessage.ApplicationMessage,
queuedApplicationMessage.SubscriptionQualityOfServiceLevel);
if (_serverOptions.ClientMessageQueueInterceptor != null)
{
await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false);
}
if (!context.AcceptEnqueue || context.ApplicationMessage == null)
{
return;
}
publishPacket.Topic = context.ApplicationMessage.Topic;
publishPacket.Payload = context.ApplicationMessage.Payload;
publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
}
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
{
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
var awaiter = _packetDispatcher.AddAwaiter<MqttPubAckPacket>(publishPacket.PacketIdentifier);
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
using (var awaiter1 = _packetDispatcher.AddAwaiter<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(publishPacket.PacketIdentifier))
{
await SendAsync(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
await SendAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
_logger.Verbose("Client '{0}': Queued application message sent.", ClientId);
} }
} }
catch (Exception exception) catch (Exception exception)
{ {
if (exception is OperationCanceledException) _logger.Warning(exception, "Client '{{0}}': Error while sending DISCONNECT packet (Reason = {1}).", ClientId, reason);
{
}
else if (exception is MqttCommunicationTimedOutException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Timeout.", ClientId);
}
else if (exception is MqttCommunicationException)
{
_logger.Warning(exception, "Client '{0}': Sending publish packet failed: Communication exception.", ClientId);
}
else
{
_logger.Error(exception, "Client '{0}': Sending publish packet failed.", ClientId);
}
if (publishPacket?.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
{
queuedApplicationMessage.IsDuplicate = true;
Session.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
}
StopInternal();
} }
} }
Task SendAsync(MqttBasePacket packet, CancellationToken cancellationToken) async Task<MqttPublishPacket> InvokeClientMessageQueueInterceptor(MqttPublishPacket publishPacket, MqttQueuedApplicationMessage queuedApplicationMessage)
{ {
return _channelAdapter.SendPacketAsync(packet, cancellationToken).ContinueWith(task => if (_serverOptions.ClientMessageQueueInterceptor == null)
{ {
Interlocked.Increment(ref _receivedPacketsCount); return publishPacket;
}
if (packet is MqttPublishPacket) var context = new MqttClientMessageQueueInterceptorContext
{ {
Interlocked.Increment(ref _receivedApplicationMessagesCount); SenderClientId = queuedApplicationMessage.SenderClientId,
} ReceiverClientId = ClientId,
}, cancellationToken); ApplicationMessage = queuedApplicationMessage.ApplicationMessage,
SubscriptionQualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel
};
if (_serverOptions.ClientMessageQueueInterceptor != null)
{
await _serverOptions.ClientMessageQueueInterceptor.InterceptClientMessageQueueEnqueueAsync(context).ConfigureAwait(false);
}
if (!context.AcceptEnqueue || context.ApplicationMessage == null)
{
return null;
}
publishPacket.Topic = context.ApplicationMessage.Topic;
publishPacket.Payload = context.ApplicationMessage.Payload;
publishPacket.QualityOfServiceLevel = context.SubscriptionQualityOfServiceLevel;
return publishPacket;
} }
} }
} }
@@ -0,0 +1,90 @@
using System;
using System.Threading;
using MQTTnet.Packets;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientConnectionStatistics
{
readonly DateTime _connectedTimestamp;
DateTime _lastNonKeepAlivePacketReceivedTimestamp;
DateTime _lastPacketReceivedTimestamp;
DateTime _lastPacketSentTimestamp;
// Start with 1 because the CONNACK packet is not counted here.
long _receivedPacketsCount = 1;
// Start with 1 because the CONNECT packet is not counted here.
long _sentPacketsCount = 1;
long _receivedApplicationMessagesCount;
long _sentApplicationMessagesCount;
public MqttClientConnectionStatistics()
{
_connectedTimestamp = DateTime.UtcNow;
_lastPacketReceivedTimestamp = _connectedTimestamp;
_lastPacketSentTimestamp = _connectedTimestamp;
_lastNonKeepAlivePacketReceivedTimestamp = _connectedTimestamp;
}
public DateTime LastPacketReceivedTimestamp => _lastPacketReceivedTimestamp;
public void HandleReceivedPacket(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
// This class is tracking all values from Clients perspective!
_lastPacketSentTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _sentPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _sentApplicationMessagesCount);
}
if (!(packet is MqttPingReqPacket || packet is MqttPingRespPacket))
{
_lastNonKeepAlivePacketReceivedTimestamp = _lastPacketReceivedTimestamp;
}
}
public void HandleSentPacket(MqttBasePacket packet)
{
if (packet == null) throw new ArgumentNullException(nameof(packet));
// This class is tracking all values from Clients perspective!
_lastPacketReceivedTimestamp = DateTime.UtcNow;
Interlocked.Increment(ref _receivedPacketsCount);
if (packet is MqttPublishPacket)
{
Interlocked.Increment(ref _receivedApplicationMessagesCount);
}
}
public void FillClientStatus(MqttClientStatus clientStatus)
{
if (clientStatus == null) throw new ArgumentNullException(nameof(clientStatus));
clientStatus.ConnectedTimestamp = _connectedTimestamp;
clientStatus.ReceivedPacketsCount = Interlocked.Read(ref _receivedPacketsCount);
clientStatus.SentPacketsCount = Interlocked.Read(ref _sentPacketsCount);
clientStatus.ReceivedApplicationMessagesCount = Interlocked.Read(ref _receivedApplicationMessagesCount);
clientStatus.SentApplicationMessagesCount = Interlocked.Read(ref _sentApplicationMessagesCount);
clientStatus.LastPacketReceivedTimestamp = _lastPacketReceivedTimestamp;
clientStatus.LastPacketSentTimestamp = _lastPacketSentTimestamp;
clientStatus.LastNonKeepAlivePacketReceivedTimestamp = _lastNonKeepAlivePacketReceivedTimestamp;
}
}
}
@@ -1,10 +1,10 @@
using MQTTnet.Diagnostics; using System;
using MQTTnet.Server.Status;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Server.Status;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttClientSession public sealed class MqttClientSession
{ {
@@ -81,7 +81,7 @@ namespace MQTTnet.Server
return SubscriptionsManager.UnsubscribeAsync(topicFilters); return SubscriptionsManager.UnsubscribeAsync(topicFilters);
} }
public void FillStatus(MqttSessionStatus status) public void FillSessionStatus(MqttSessionStatus status)
{ {
status.ClientId = ClientId; status.ClientId = ClientId;
status.CreatedTimestamp = _createdTimestamp; status.CreatedTimestamp = _createdTimestamp;
@@ -1,10 +1,10 @@
using MQTTnet.Internal; using System;
using MQTTnet.Protocol;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable public sealed class MqttClientSessionApplicationMessagesQueue : IDisposable
{ {
@@ -1,4 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
using MQTTnet.Exceptions; using MQTTnet.Exceptions;
using MQTTnet.Formatter; using MQTTnet.Formatter;
@@ -6,15 +13,8 @@ using MQTTnet.Internal;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server.Status; using MQTTnet.Server.Status;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttClientSessionsManager : IDisposable public sealed class MqttClientSessionsManager : IDisposable
{ {
@@ -53,53 +53,66 @@ namespace MQTTnet.Server
Task.Run(() => TryProcessQueuedApplicationMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger); Task.Run(() => TryProcessQueuedApplicationMessagesAsync(cancellationToken), cancellationToken).RunInBackground(_logger);
} }
public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) async Task<MqttConnectPacket> ReceiveConnectPacket(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{ {
try try
{ {
MqttConnectPacket connectPacket; using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout))
try using (var effectiveCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancellationToken))
{ {
using (var timeoutToken = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) var firstPacket = await channelAdapter.ReceivePacketAsync(effectiveCancellationToken.Token).ConfigureAwait(false);
{
var firstPacket = await channelAdapter.ReceivePacketAsync(timeoutToken.Token).ConfigureAwait(false);
connectPacket = firstPacket as MqttConnectPacket;
if (connectPacket == null)
{
_logger.Warning(null,
"Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].",
channelAdapter.Endpoint);
return; if (firstPacket is MqttConnectPacket connectPacket)
} {
return connectPacket;
} }
} }
catch (OperationCanceledException) }
catch (OperationCanceledException)
{
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning("Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
}
_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;
try
{
var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false);
if (connectPacket == null)
{ {
_logger.Warning(null, "Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint); // Nothing was received in time etc.
return;
}
catch (MqttCommunicationTimedOutException)
{
_logger.Warning(null, "Client '{0}': Connected but did not sent a CONNECT packet.", channelAdapter.Endpoint);
return; return;
} }
var connectionValidatorContext = await ValidateConnectionAsync(connectPacket, channelAdapter).ConfigureAwait(false); MqttConnAckPacket connAckPacket;
var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success) if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
{ {
// Send failure response here without preparing a session. The result for a successful connect // Send failure response here without preparing a session!
// will be sent from the session itself. connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
var connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
return; return;
} }
var connection = await CreateClientConnectionAsync(connectPacket, connectionValidatorContext, channelAdapter).ConfigureAwait(false); clientConnection = await CreateClientConnection(connectPacket, channelAdapter, connectionValidatorContext.SessionItems).ConfigureAwait(false);
await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket.ClientId).ConfigureAwait(false);
await connection.RunAsync().ConfigureAwait(false); connAckPacket = channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);
await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);
await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket, channelAdapter).ConfigureAwait(false);
await clientConnection.RunAsync().ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -108,6 +121,41 @@ namespace MQTTnet.Server
{ {
_logger.Error(exception, exception.Message); _logger.Error(exception, exception.Message);
} }
finally
{
if (clientConnection != null)
{
if (clientConnection.ClientId != null)
{
// in case it is a takeover _connections already contains the new connection
if (!clientConnection.IsTakenOver)
{
lock (_connections)
{
_connections.Remove(clientConnection.ClientId);
}
if (!_options.EnablePersistentSessions)
{
await DeleteSessionAsync(clientConnection.ClientId).ConfigureAwait(false);
}
}
}
var endpoint = clientConnection.Endpoint;
if (clientConnection.ClientId != null && !clientConnection.IsTakenOver)
{
// The event is fired at a separate place in case of a handover!
await _eventDispatcher.SafeNotifyClientDisconnectedAsync(
clientConnection.ClientId,
clientConnection.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean,
endpoint).ConfigureAwait(false);
}
}
await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
} }
public async Task CloseAllConnectionsAsync() public async Task CloseAllConnectionsAsync()
@@ -142,10 +190,10 @@ namespace MQTTnet.Server
foreach (var connection in _connections.Values) foreach (var connection in _connections.Values)
{ {
var clientStatus = new MqttClientStatus(connection); var clientStatus = new MqttClientStatus(connection);
connection.FillStatus(clientStatus); connection.FillClientStatus(clientStatus);
var sessionStatus = new MqttSessionStatus(connection.Session, this); var sessionStatus = new MqttSessionStatus(connection.Session, this);
connection.Session.FillStatus(sessionStatus); connection.Session.FillSessionStatus(sessionStatus);
clientStatus.Session = sessionStatus; clientStatus.Session = sessionStatus;
result.Add(clientStatus); result.Add(clientStatus);
@@ -164,7 +212,7 @@ namespace MQTTnet.Server
foreach (var session in _sessions.Values) foreach (var session in _sessions.Values)
{ {
var sessionStatus = new MqttSessionStatus(session, this); var sessionStatus = new MqttSessionStatus(session, this);
session.FillStatus(sessionStatus); session.FillSessionStatus(sessionStatus);
result.Add(sessionStatus); result.Add(sessionStatus);
} }
@@ -217,35 +265,6 @@ namespace MQTTnet.Server
_logger.Verbose("Session for client '{0}' deleted.", clientId); _logger.Verbose("Session for client '{0}' deleted.", clientId);
} }
public async Task CleanUpClient(string clientId, IMqttChannelAdapter channelAdapter, MqttClientDisconnectType disconnectType)
{
if (clientId != null)
{
// in case it is a takeover _connections already contains the new connection
if (disconnectType != MqttClientDisconnectType.Takeover)
{
lock (_connections)
{
_connections.Remove(clientId);
}
if (!_options.EnablePersistentSessions)
{
await DeleteSessionAsync(clientId).ConfigureAwait(false);
}
}
}
var endpoint = channelAdapter.Endpoint;
await SafeCleanupChannelAsync(channelAdapter).ConfigureAwait(false);
if (clientId != null)
{
await _eventDispatcher.SafeNotifyClientDisconnectedAsync(clientId, disconnectType, endpoint).ConfigureAwait(false);
}
}
public void Dispose() public void Dispose()
{ {
_messageQueue?.Dispose(); _messageQueue?.Dispose();
@@ -278,7 +297,7 @@ namespace MQTTnet.Server
MqttPendingApplicationMessage queuedApplicationMessage; MqttPendingApplicationMessage queuedApplicationMessage;
try try
{ {
queuedApplicationMessage = _messageQueue.Take(cancellationToken); queuedApplicationMessage = _messageQueue.Take(cancellationToken);
} }
catch (ArgumentNullException) catch (ArgumentNullException)
{ {
@@ -360,9 +379,12 @@ namespace MQTTnet.Server
} }
} }
async Task<MqttConnectionValidatorContext> ValidateConnectionAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter) async Task<MqttConnectionValidatorContext> ValidateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{ {
var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter, new ConcurrentDictionary<object, object>()); var context = new MqttConnectionValidatorContext(connectPacket, channelAdapter)
{
SessionItems = new ConcurrentDictionary<object, object>()
};
var connectionValidator = _options.ConnectionValidator; var connectionValidator = _options.ConnectionValidator;
@@ -388,9 +410,8 @@ namespace MQTTnet.Server
return context; return context;
} }
async Task<MqttClientConnection> CreateClientConnectionAsync(MqttConnectPacket connectPacket, MqttConnectionValidatorContext connectionValidatorContext, IMqttChannelAdapter channelAdapter) async Task<MqttClientConnection> CreateClientConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, IDictionary<object, object> sessionItems)
{ {
MqttClientConnection existingConnection;
MqttClientConnection connection; MqttClientConnection connection;
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false)) using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
@@ -401,14 +422,14 @@ namespace MQTTnet.Server
if (!_sessions.TryGetValue(connectPacket.ClientId, out session)) if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
{ {
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId); _logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, connectionValidatorContext); session = CreateSession(connectPacket.ClientId, sessionItems);
} }
else else
{ {
if (connectPacket.CleanSession) if (connectPacket.CleanSession)
{ {
_logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId); _logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, connectionValidatorContext); session = CreateSession(connectPacket.ClientId, sessionItems);
} }
else else
{ {
@@ -419,16 +440,21 @@ namespace MQTTnet.Server
_sessions[connectPacket.ClientId] = session; _sessions[connectPacket.ClientId] = session;
} }
MqttClientConnection existingConnection;
lock (_connections) lock (_connections)
{ {
_connections.TryGetValue(connectPacket.ClientId, out existingConnection); _connections.TryGetValue(connectPacket.ClientId, out existingConnection);
connection = CreateConnection(connectPacket, channelAdapter, session, connectionValidatorContext); connection = CreateConnection(connectPacket, channelAdapter, session);
_connections[connectPacket.ClientId] = connection; _connections[connectPacket.ClientId] = connection;
} }
if (existingConnection != null) if (existingConnection != null)
{ {
await _eventDispatcher.SafeNotifyClientDisconnectedAsync(existingConnection.ClientId, MqttClientDisconnectType.Takeover, existingConnection.Endpoint);
existingConnection.IsTakenOver = true;
await existingConnection.StopAsync(MqttClientDisconnectReason.SessionTakenOver).ConfigureAwait(false); await existingConnection.StopAsync(MqttClientDisconnectReason.SessionTakenOver).ConfigureAwait(false);
} }
} }
@@ -436,7 +462,8 @@ namespace MQTTnet.Server
return connection; return connection;
} }
async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection, MqttApplicationMessage applicationMessage) async Task<MqttApplicationMessageInterceptorContext> InterceptApplicationMessageAsync(IMqttServerApplicationMessageInterceptor interceptor, MqttClientConnection clientConnection,
MqttApplicationMessage applicationMessage)
{ {
string senderClientId; string senderClientId;
IDictionary<object, object> sessionItems; IDictionary<object, object> sessionItems;
@@ -453,29 +480,20 @@ namespace MQTTnet.Server
sessionItems = clientConnection.Session.Items; sessionItems = clientConnection.Session.Items;
} }
var interceptorContext = new MqttApplicationMessageInterceptorContext(senderClientId, sessionItems, _logger) var interceptorContext = new MqttApplicationMessageInterceptorContext
{ {
ClientId = senderClientId,
SessionItems = sessionItems,
Logger = _logger,
AcceptPublish = true, AcceptPublish = true,
ApplicationMessage = applicationMessage, ApplicationMessage = applicationMessage,
CloseConnection = false CloseConnection = false
}; };
await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false); await interceptor.InterceptApplicationMessagePublishAsync(interceptorContext).ConfigureAwait(false);
return interceptorContext; return interceptorContext;
} }
async Task SafeCleanupChannelAsync(IMqttChannelAdapter channelAdapter)
{
try
{
await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.Error(exception, "Error while disconnecting client channel.");
}
}
MqttClientSession GetSession(string clientId) MqttClientSession GetSession(string clientId)
{ {
lock (_sessions) lock (_sessions)
@@ -489,24 +507,23 @@ namespace MQTTnet.Server
} }
} }
MqttClientConnection CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttClientSession session, MqttConnectionValidatorContext connectionValidatorContext) MqttClientConnection CreateConnection(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter, MqttClientSession session)
{ {
return new MqttClientConnection( return new MqttClientConnection(
connectPacket, connectPacket,
channelAdapter, channelAdapter,
session, session,
connectionValidatorContext,
_options, _options,
this, this,
_retainedMessagesManager, _retainedMessagesManager,
_rootLogger); _rootLogger);
} }
MqttClientSession CreateSession(string clientId, MqttConnectionValidatorContext connectionValidatorContext) MqttClientSession CreateSession(string clientId, IDictionary<object, object> sessionItems)
{ {
return new MqttClientSession( return new MqttClientSession(
clientId, clientId,
connectionValidatorContext.SessionItems, sessionItems,
_eventDispatcher, _eventDispatcher,
_options, _options,
_retainedMessagesManager, _retainedMessagesManager,
@@ -1,12 +1,12 @@
using MQTTnet.Packets; using System;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Packets;
using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttClientSubscriptionsManager public sealed class MqttClientSubscriptionsManager
{ {
@@ -37,9 +37,11 @@ namespace MQTTnet.Server
{ {
var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false); var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false);
var finalTopicFilter = interceptorContext.TopicFilter; var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter;
var acceptSubscription = interceptorContext?.AcceptSubscription ?? true;
var closeConnection = interceptorContext?.CloseConnection ?? false;
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !interceptorContext.AcceptSubscription) if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
{ {
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure); result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError); result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
@@ -50,12 +52,12 @@ namespace MQTTnet.Server
result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel)); result.ReasonCodes.Add(ConvertToSubscribeReasonCode(finalTopicFilter.QualityOfServiceLevel));
} }
if (interceptorContext.CloseConnection) if (closeConnection)
{ {
result.CloseConnection = true; result.CloseConnection = true;
} }
if (!interceptorContext.AcceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic)) if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
{ {
continue; continue;
} }
@@ -83,12 +85,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in topicFilters) foreach (var topicFilter in topicFilters)
{ {
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false); var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptSubscription) if (interceptorContext != null && !interceptorContext.AcceptSubscription)
{
continue;
}
if (!interceptorContext.AcceptSubscription)
{ {
continue; continue;
} }
@@ -116,7 +113,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in unsubscribePacket.TopicFilters) foreach (var topicFilter in unsubscribePacket.TopicFilters)
{ {
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptUnsubscription) if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{ {
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError); reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
continue; continue;
@@ -150,7 +147,7 @@ namespace MQTTnet.Server
foreach (var topicFilter in topicFilters) foreach (var topicFilter in topicFilters)
{ {
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false); var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (!interceptorContext.AcceptUnsubscription) if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{ {
continue; continue;
} }
@@ -211,23 +208,41 @@ namespace MQTTnet.Server
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter) async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
{ {
var context = new MqttSubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items); var interceptor = _options.SubscriptionInterceptor;
if (_options.SubscriptionInterceptor != null) if (interceptor == null)
{ {
await _options.SubscriptionInterceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false); return null;
} }
var context = new MqttSubscriptionInterceptorContext
{
ClientId = _clientSession.ClientId,
TopicFilter = topicFilter,
SessionItems = _clientSession.Items
};
await interceptor.InterceptSubscriptionAsync(context).ConfigureAwait(false);
return context; return context;
} }
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter) async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
{ {
var context = new MqttUnsubscriptionInterceptorContext(_clientSession.ClientId, topicFilter, _clientSession.Items); var interceptor = _options.UnsubscriptionInterceptor;
if (_options.UnsubscriptionInterceptor != null) if (interceptor == null)
{ {
await _options.UnsubscriptionInterceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false); return null;
} }
var context = new MqttUnsubscriptionInterceptorContext
{
ClientId = _clientSession.ClientId,
Topic = topicFilter,
SessionItems = _clientSession.Items
};
await interceptor.InterceptUnsubscriptionAsync(context).ConfigureAwait(false);
return context; return context;
} }
@@ -1,13 +1,13 @@
using MQTTnet.Diagnostics; using System;
using MQTTnet.Implementations;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
{ {
@@ -1,9 +1,11 @@
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Adapter;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Packets;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttServerEventDispatcher public sealed class MqttServerEventDispatcher
{ {
@@ -26,7 +28,7 @@ namespace MQTTnet.Server
public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; } public IMqttApplicationMessageReceivedHandler ApplicationMessageReceivedHandler { get; set; }
public async Task SafeNotifyClientConnectedAsync(string clientId) public async Task SafeNotifyClientConnectedAsync(MqttConnectPacket connectPacket, IMqttChannelAdapter channelAdapter)
{ {
try try
{ {
@@ -36,7 +38,13 @@ namespace MQTTnet.Server
return; return;
} }
await handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs(clientId)).ConfigureAwait(false); await handler.HandleClientConnectedAsync(new MqttServerClientConnectedEventArgs
{
ClientId = connectPacket.ClientId,
UserName = connectPacket.Username,
ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion,
Endpoint = channelAdapter.Endpoint
}).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -54,7 +62,12 @@ namespace MQTTnet.Server
return; return;
} }
await handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs(clientId, disconnectType, endpoint)).ConfigureAwait(false); await handler.HandleClientDisconnectedAsync(new MqttServerClientDisconnectedEventArgs
{
ClientId = clientId,
DisconnectType = disconnectType,
Endpoint = endpoint
}).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -72,7 +85,11 @@ namespace MQTTnet.Server
return; return;
} }
await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false); await handler.HandleClientSubscribedTopicAsync(new MqttServerClientSubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -90,7 +107,11 @@ namespace MQTTnet.Server
return; return;
} }
await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs(clientId, topicFilter)).ConfigureAwait(false); await handler.HandleClientUnsubscribedTopicAsync(new MqttServerClientUnsubscribedTopicEventArgs
{
ClientId = clientId,
TopicFilter = topicFilter
}).ConfigureAwait(false);
} }
catch (Exception exception) catch (Exception exception)
{ {
@@ -1,12 +1,12 @@
using MQTTnet.Diagnostics; using System;
using MQTTnet.Internal;
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public sealed class MqttServerKeepAliveMonitor public sealed class MqttServerKeepAliveMonitor
{ {
@@ -70,7 +70,7 @@ namespace MQTTnet.Server
{ {
try try
{ {
if (connection.Status != MqttClientConnectionStatus.Running) if (!connection.IsRunning)
{ {
// The connection is already dead or just created so there is no need to check it. // The connection is already dead or just created so there is no need to check it.
return; return;
@@ -94,14 +94,14 @@ namespace MQTTnet.Server
// If the client sends 1 sec. the server will allow up to 1.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.ConnectPacket.KeepAlivePeriod * 1.5D;
var secondsWithoutPackage = (now - connection.LastPacketReceivedTimestamp).TotalSeconds; var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds;
if (secondsWithoutPackage < maxDurationWithoutPacket) if (secondsWithoutPackage < maxDurationWithoutPacket)
{ {
// A packet was received before the timeout is affected. // A packet was received before the timeout is affected.
return; return;
} }
_logger.Warning(null, "Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId); _logger.Warning("Client '{0}': Did not receive any packet or keep alive signal.", connection.ClientId);
// Execute the disconnection in background so that the keep alive monitor can continue // Execute the disconnection in background so that the keep alive monitor can continue
// with checking other connections. // with checking other connections.
@@ -1,6 +1,6 @@
using System; using System;
namespace MQTTnet.Server namespace MQTTnet.Server.Internal
{ {
public static class MqttTopicFilterComparer public static class MqttTopicFilterComparer
{ {
@@ -1,35 +1,27 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using MQTTnet.Diagnostics; using MQTTnet.Diagnostics;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public sealed class MqttApplicationMessageInterceptorContext public sealed class MqttApplicationMessageInterceptorContext
{ {
public MqttApplicationMessageInterceptorContext(string clientId, IDictionary<object, object> sessionItems, IMqttNetScopedLogger logger)
{
ClientId = clientId;
SessionItems = sessionItems;
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary> /// <summary>
/// Gets the currently used logger. /// Gets the currently used logger.
/// </summary> /// </summary>
public IMqttNetScopedLogger Logger { get; } public IMqttNetScopedLogger Logger { get; internal set; }
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
public MqttApplicationMessage ApplicationMessage { get; set; } public MqttApplicationMessage ApplicationMessage { get; set; }
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary<object, object> SessionItems { get; } public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptPublish { get; set; } = true; public bool AcceptPublish { get; set; } = true;
@@ -1,11 +0,0 @@
namespace MQTTnet.Server
{
public enum MqttClientConnectionStatus
{
Initializing,
Running,
Finalizing
}
}
@@ -2,19 +2,11 @@
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttClientMessageQueueInterceptorContext public sealed class MqttClientMessageQueueInterceptorContext
{ {
public MqttClientMessageQueueInterceptorContext(string senderClientId, string receiverClientId, MqttApplicationMessage applicationMessage, MqttQualityOfServiceLevel subscriptionQualityOfServiceLevel) public string SenderClientId { get; internal set; }
{
SenderClientId = senderClientId;
ReceiverClientId = receiverClientId;
ApplicationMessage = applicationMessage;
SubscriptionQualityOfServiceLevel = subscriptionQualityOfServiceLevel;
}
public string SenderClientId { get; } public string ReceiverClientId { get; internal set; }
public string ReceiverClientId { get; }
public MqttApplicationMessage ApplicationMessage { get; set; } public MqttApplicationMessage ApplicationMessage { get; set; }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor public sealed class MqttClientMessageQueueInterceptorDelegate : IMqttServerClientMessageQueueInterceptor
{ {
private readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback; readonly Func<MqttClientMessageQueueInterceptorContext, Task> _callback;
public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback) public MqttClientMessageQueueInterceptorDelegate(Action<MqttClientMessageQueueInterceptorContext> callback)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context => _callback = context =>
{ {
callback(context); callback(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -4,21 +4,21 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using MQTTnet.Adapter; using MQTTnet.Adapter;
using MQTTnet.Formatter; using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Packets; using MQTTnet.Packets;
using MQTTnet.Protocol; using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttConnectionValidatorContext public sealed class MqttConnectionValidatorContext
{ {
private readonly MqttConnectPacket _connectPacket; readonly MqttConnectPacket _connectPacket;
private readonly IMqttChannelAdapter _clientAdapter; readonly IMqttChannelAdapter _clientAdapter;
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter, IDictionary<object, object> sessionItems) public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{ {
_connectPacket = connectPacket; _connectPacket = connectPacket;
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter)); _clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
SessionItems = sessionItems;
} }
/// <summary> /// <summary>
@@ -39,7 +39,7 @@ namespace MQTTnet.Server
public byte[] RawPassword => _connectPacket?.Password; public byte[] RawPassword => _connectPacket?.Password;
public string Password => Encoding.UTF8.GetString(RawPassword ?? new byte[0]); public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
/// <summary> /// <summary>
/// Gets or sets the will delay interval. /// Gets or sets the will delay interval.
@@ -128,7 +128,7 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary<object, object> SessionItems { get; } public IDictionary<object, object> SessionItems { get; internal set; }
/// <summary> /// <summary>
/// This is used for MQTTv3 only. /// This is used for MQTTv3 only.
@@ -1,4 +1,6 @@
namespace MQTTnet.Server using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{ {
public sealed class MqttPendingApplicationMessage public sealed class MqttPendingApplicationMessage
{ {
@@ -3,7 +3,7 @@ using MQTTnet.Protocol;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttQueuedApplicationMessage public sealed class MqttQueuedApplicationMessage
{ {
public MqttApplicationMessage ApplicationMessage { get; set; } public MqttApplicationMessage ApplicationMessage { get; set; }
+1
View File
@@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations; using MQTTnet.Implementations;
using MQTTnet.Internal; using MQTTnet.Internal;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
@@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context => _callback = context =>
{ {
callback(context); callback(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -1,18 +1,29 @@
using System; using System;
using MQTTnet.Formatter;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientConnectedEventArgs : EventArgs public sealed class MqttServerClientConnectedEventArgs : EventArgs
{ {
public MqttServerClientConnectedEventArgs(string clientId)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier of the connected client.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
/// <summary>
/// Gets the user name of the connected client.
/// </summary>
public string UserName { get; internal set; }
/// <summary>
/// Gets the protocol version which is used by the connected client.
/// </summary>
public MqttProtocolVersion ProtocolVersion { get; internal set; }
/// <summary>
/// Gets the endpoint of the connected client.
/// </summary>
public string Endpoint { get; internal set; }
} }
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler public sealed class MqttServerClientConnectedHandlerDelegate : IMqttServerClientConnectedHandler
{ {
private readonly Func<MqttServerClientConnectedEventArgs, Task> _handler; readonly Func<MqttServerClientConnectedEventArgs, Task> _handler;
public MqttServerClientConnectedHandlerDelegate(Action<MqttServerClientConnectedEventArgs> handler) public MqttServerClientConnectedHandlerDelegate(Action<MqttServerClientConnectedEventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -2,23 +2,16 @@ using System;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientDisconnectedEventArgs : EventArgs public sealed class MqttServerClientDisconnectedEventArgs : EventArgs
{ {
public MqttServerClientDisconnectedEventArgs(string clientId, MqttClientDisconnectType disconnectType, string endpoint)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
DisconnectType = disconnectType;
Endpoint = endpoint;
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
public MqttClientDisconnectType DisconnectType { get; } public MqttClientDisconnectType DisconnectType { get; internal set; }
public string Endpoint { get; } public string Endpoint { get; internal set; }
} }
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler public sealed class MqttServerClientDisconnectedHandlerDelegate : IMqttServerClientDisconnectedHandler
{ {
private readonly Func<MqttServerClientDisconnectedEventArgs, Task> _handler; readonly Func<MqttServerClientDisconnectedEventArgs, Task> _handler;
public MqttServerClientDisconnectedHandlerDelegate(Action<MqttServerClientDisconnectedEventArgs> handler) public MqttServerClientDisconnectedHandlerDelegate(Action<MqttServerClientDisconnectedEventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -2,24 +2,18 @@
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientSubscribedTopicEventArgs : EventArgs public sealed class MqttServerClientSubscribedTopicEventArgs : EventArgs
{ {
public MqttServerClientSubscribedTopicEventArgs(string clientId, MqttTopicFilter topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
/// <summary> /// <summary>
/// Gets the topic filter. /// Gets the topic filter.
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public MqttTopicFilter TopicFilter { get; } public MqttTopicFilter TopicFilter { get; internal set; }
} }
} }
@@ -1,24 +1,37 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientSubscribedHandlerDelegate : IMqttServerClientSubscribedTopicHandler [Obsolete("Use MqttServerClientSubscribedTopicHandlerDelegate instead. This will be removed in a future version.")]
public sealed class MqttServerClientSubscribedHandlerDelegate : MqttServerClientSubscribedTopicHandlerDelegate
{ {
private readonly Func<MqttServerClientSubscribedTopicEventArgs, Task> _handler; public MqttServerClientSubscribedHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler) : base(handler)
{
}
public MqttServerClientSubscribedHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler) public MqttServerClientSubscribedHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler) : base(handler)
{
}
}
public class MqttServerClientSubscribedTopicHandlerDelegate : IMqttServerClientSubscribedTopicHandler
{
readonly Func<MqttServerClientSubscribedTopicEventArgs, Task> _handler;
public MqttServerClientSubscribedTopicHandlerDelegate(Action<MqttServerClientSubscribedTopicEventArgs> handler)
{ {
if (handler == null) throw new ArgumentNullException(nameof(handler)); if (handler == null) throw new ArgumentNullException(nameof(handler));
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
public MqttServerClientSubscribedHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler) public MqttServerClientSubscribedTopicHandlerDelegate(Func<MqttServerClientSubscribedTopicEventArgs, Task> handler)
{ {
_handler = handler ?? throw new ArgumentNullException(nameof(handler)); _handler = handler ?? throw new ArgumentNullException(nameof(handler));
} }
@@ -2,24 +2,18 @@
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientUnsubscribedTopicEventArgs : EventArgs public sealed class MqttServerClientUnsubscribedTopicEventArgs : EventArgs
{ {
public MqttServerClientUnsubscribedTopicEventArgs(string clientId, string topicFilter)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
TopicFilter = topicFilter ?? throw new ArgumentNullException(nameof(topicFilter));
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the topic filter. /// Gets or sets the topic filter.
/// The topic filter can contain topics and wildcards. /// The topic filter can contain topics and wildcards.
/// </summary> /// </summary>
public string TopicFilter { get; } public string TopicFilter { get; internal set; }
} }
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler public sealed class MqttServerClientUnsubscribedTopicHandlerDelegate : IMqttServerClientUnsubscribedTopicHandler
{ {
private readonly Func<MqttServerClientUnsubscribedTopicEventArgs, Task> _handler; readonly Func<MqttServerClientUnsubscribedTopicEventArgs, Task> _handler;
public MqttServerClientUnsubscribedTopicHandlerDelegate(Action<MqttServerClientUnsubscribedTopicEventArgs> handler) public MqttServerClientUnsubscribedTopicHandlerDelegate(Action<MqttServerClientUnsubscribedTopicEventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator public sealed class MqttServerConnectionValidatorDelegate : IMqttServerConnectionValidator
{ {
private readonly Func<MqttConnectionValidatorContext, Task> _callback; readonly Func<MqttConnectionValidatorContext, Task> _callback;
public MqttServerConnectionValidatorDelegate(Action<MqttConnectionValidatorContext> callback) public MqttServerConnectionValidatorDelegate(Action<MqttConnectionValidatorContext> callback)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context => _callback = context =>
{ {
callback(context); callback(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -17,7 +17,7 @@ namespace MQTTnet.Server
_callback = context => _callback = context =>
{ {
callback(context); callback(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -1,4 +1,5 @@
using System; using System;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler public sealed class MqttServerStartedHandlerDelegate : IMqttServerStartedHandler
{ {
private readonly Func<EventArgs, Task> _handler; readonly Func<EventArgs, Task> _handler;
public MqttServerStartedHandlerDelegate(Action<EventArgs> handler) public MqttServerStartedHandlerDelegate(Action<EventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler public sealed class MqttServerStoppedHandlerDelegate : IMqttServerStoppedHandler
{ {
private readonly Func<EventArgs, Task> _handler; readonly Func<EventArgs, Task> _handler;
public MqttServerStoppedHandlerDelegate(Action<EventArgs> handler) public MqttServerStoppedHandlerDelegate(Action<EventArgs> handler)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_handler = eventArgs => _handler = eventArgs =>
{ {
handler(eventArgs); handler(eventArgs);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -1,11 +1,12 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Implementations;
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor public sealed class MqttServerSubscriptionInterceptorDelegate : IMqttServerSubscriptionInterceptor
{ {
private readonly Func<MqttSubscriptionInterceptorContext, Task> _callback; readonly Func<MqttSubscriptionInterceptorContext, Task> _callback;
public MqttServerSubscriptionInterceptorDelegate(Action<MqttSubscriptionInterceptorContext> callback) public MqttServerSubscriptionInterceptorDelegate(Action<MqttSubscriptionInterceptorContext> callback)
{ {
@@ -14,7 +15,7 @@ namespace MQTTnet.Server
_callback = context => _callback = context =>
{ {
callback(context); callback(context);
return Task.FromResult(0); return PlatformAbstractionLayer.CompletedTask;
}; };
} }
@@ -2,20 +2,13 @@
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttSubscriptionInterceptorContext public sealed class MqttSubscriptionInterceptorContext
{ {
public MqttSubscriptionInterceptorContext(string clientId, MqttTopicFilter topicFilter, IDictionary<object, object> sessionItems)
{
ClientId = clientId;
TopicFilter = topicFilter;
SessionItems = sessionItems;
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the topic filter. /// Gets or sets the topic filter.
@@ -26,7 +19,7 @@ namespace MQTTnet.Server
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary<object, object> SessionItems { get; } public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptSubscription { get; set; } = true; public bool AcceptSubscription { get; set; } = true;
@@ -2,32 +2,25 @@
namespace MQTTnet.Server namespace MQTTnet.Server
{ {
public class MqttUnsubscriptionInterceptorContext public sealed class MqttUnsubscriptionInterceptorContext
{ {
public MqttUnsubscriptionInterceptorContext(string clientId, string topic, IDictionary<object, object> sessionItems)
{
ClientId = clientId;
Topic = topic;
SessionItems = sessionItems;
}
/// <summary> /// <summary>
/// Gets the client identifier. /// Gets the client identifier.
/// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues. /// Hint: This identifier needs to be unique over all used clients / devices on the broker to avoid connection issues.
/// </summary> /// </summary>
public string ClientId { get; } public string ClientId { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets the MQTT topic. /// Gets or sets the MQTT topic.
/// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client. /// In MQTT, the word topic refers to an UTF-8 string that the broker uses to filter messages for each connected client.
/// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator). /// The topic consists of one or more topic levels. Each topic level is separated by a forward slash (topic level separator).
/// </summary> /// </summary>
public string Topic { get; set; } public string Topic { get; internal set; }
/// <summary> /// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session. /// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary> /// </summary>
public IDictionary<object, object> SessionItems { get; } public IDictionary<object, object> SessionItems { get; internal set; }
public bool AcceptUnsubscription { get; set; } = true; public bool AcceptUnsubscription { get; set; } = true;
@@ -1,9 +0,0 @@
namespace MQTTnet.Server
{
public class PrepareClientSessionResult
{
public bool IsExistingSession { get; set; }
public MqttClientConnection Session { get; set; }
}
}
@@ -16,8 +16,12 @@ namespace MQTTnet.Server.Status
MqttProtocolVersion ProtocolVersion { get; } MqttProtocolVersion ProtocolVersion { get; }
DateTime ConnectedTimestamp { get; set; }
DateTime LastPacketReceivedTimestamp { get; } DateTime LastPacketReceivedTimestamp { get; }
DateTime LastPacketSentTimestamp { get; set; }
DateTime LastNonKeepAlivePacketReceivedTimestamp { get; } DateTime LastNonKeepAlivePacketReceivedTimestamp { get; }
long ReceivedApplicationMessagesCount { get; } long ReceivedApplicationMessagesCount { get; }
@@ -11,6 +11,9 @@ namespace MQTTnet.Server.Status
/// </summary> /// </summary>
string ClientId { get; } string ClientId { get; }
/// <summary>
/// Gets the count of messages which are not yet sent to the client but already queued.
/// </summary>
long PendingApplicationMessagesCount { get; } long PendingApplicationMessagesCount { get; }
IDictionary<object, object> Items { get; } IDictionary<object, object> Items { get; }
@@ -2,6 +2,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client.Disconnecting; using MQTTnet.Client.Disconnecting;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server.Status namespace MQTTnet.Server.Status
{ {
@@ -24,9 +25,11 @@ namespace MQTTnet.Server.Status
public MqttProtocolVersion ProtocolVersion { get; set; } public MqttProtocolVersion ProtocolVersion { get; set; }
public DateTime ConnectedTimestamp { get; set; }
public DateTime LastPacketReceivedTimestamp { get; set; } public DateTime LastPacketReceivedTimestamp { get; set; }
public DateTime ConnectedTimestamp { get; set; } public DateTime LastPacketSentTimestamp { get; set; }
public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; } public DateTime LastNonKeepAlivePacketReceivedTimestamp { get; set; }
@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Server.Internal;
namespace MQTTnet.Server.Status namespace MQTTnet.Server.Status
{ {
@@ -25,6 +26,9 @@ namespace MQTTnet.Server.Status
public DateTime CreatedTimestamp { get; set; } public DateTime CreatedTimestamp { get; set; }
/// <summary>
/// This items can be used by the library user in order to store custom information.
/// </summary>
public IDictionary<object, object> Items { get; set; } public IDictionary<object, object> Items { get; set; }
public Task DeleteAsync() public Task DeleteAsync()
@@ -2,6 +2,7 @@
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using MQTTnet.Server; using MQTTnet.Server;
using System; using System;
using MQTTnet.Server.Internal;
namespace MQTTnet.Benchmarks namespace MQTTnet.Benchmarks
{ {
+22
View File
@@ -0,0 +1,22 @@
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests
{
public abstract class BaseTestClass
{
public TestContext TestContext { get; set; }
protected TestEnvironment CreateTestEnvironment()
{
return new TestEnvironment(TestContext);
}
protected Task LongDelay()
{
return Task.Delay(TimeSpan.FromSeconds(1));
}
}
}
@@ -16,7 +16,7 @@ using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.MQTTv5 namespace MQTTnet.Tests.MQTTv5
{ {
[TestClass] [TestClass]
public class Client_Tests public sealed class Client_Tests
{ {
public TestContext TestContext { get; set; } public TestContext TestContext { get; set; }
@@ -60,6 +60,7 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(2, receivedMessage.UserProperties.Count); Assert.AreEqual(2, receivedMessage.UserProperties.Count);
} }
} }
[TestMethod] [TestMethod]
public async Task Connect_With_AssignedClientId() public async Task Connect_With_AssignedClientId()
{ {
@@ -116,7 +117,6 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual("test123", serverConnectedClientId); Assert.AreEqual("test123", serverConnectedClientId);
Assert.AreEqual("test123", serverDisconnectedClientId); Assert.AreEqual("test123", serverDisconnectedClientId);
Assert.AreEqual("test123", clientAssignedClientId); Assert.AreEqual("test123", clientAssignedClientId);
} }
} }
@@ -165,7 +165,6 @@ namespace MQTTnet.Tests.MQTTv5
Assert.AreEqual(1, result.Items.Count); Assert.AreEqual(1, result.Items.Count);
Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode); Assert.AreEqual(MqttClientSubscribeResultCode.GrantedQoS1, result.Items[0].ResultCode);
} }
} }
@@ -172,21 +172,25 @@ namespace MQTTnet.Tests.Mockups
return StartServerAsync(new MqttServerOptionsBuilder()); return StartServerAsync(new MqttServerOptionsBuilder());
} }
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options) public IMqttServer CreateServer()
{ {
if (options == null) throw new ArgumentNullException(nameof(options));
if (Server != null) if (Server != null)
{ {
throw new InvalidOperationException("Server already started."); throw new InvalidOperationException("Server already started.");
} }
Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this); Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
return Server;
}
public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
{
CreateServer();
options.WithDefaultEndpointPort(ServerPort); options.WithDefaultEndpointPort(ServerPort);
options.WithMaxPendingMessagesPerClient(int.MaxValue); options.WithMaxPendingMessagesPerClient(int.MaxValue);
await Server.StartAsync(options.Build()).ConfigureAwait(false); await Server.StartAsync(options.Build());
return Server; return Server;
} }
+1 -1
View File
@@ -50,7 +50,7 @@ namespace MQTTnet.Tests
Topic = "Test" Topic = "Test"
}, CancellationToken.None); }, CancellationToken.None);
await Task.Delay(1000); await Task.Delay(500);
// This will simulate a device which closes the connection directly // This will simulate a device which closes the connection directly
// after sending the data so do delay is added between send and dispose! // after sending the data so do delay is added between send and dispose!
@@ -5,6 +5,7 @@ using MQTTnet.Server;
using MQTTnet.Tests.Mockups; using MQTTnet.Tests.Mockups;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Server.Internal;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
@@ -0,0 +1,186 @@
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Client.Receiving;
using MQTTnet.Formatter;
using MQTTnet.Protocol;
using MQTTnet.Server;
namespace MQTTnet.Tests
{
[TestClass]
public sealed class Server_Events_Tests : BaseTestClass
{
[TestMethod]
public async Task Fire_Client_Connected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientConnectedEventArgs eventArgs = null;
server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e =>
{
eventArgs = e;
});
await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser"));
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Connected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("::1"));
Assert.AreEqual(MqttProtocolVersion.V311, eventArgs.ProtocolVersion);
Assert.AreEqual("TheUser", eventArgs.UserName);
}
}
[TestMethod]
public async Task Fire_Client_Disconnected_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientDisconnectedEventArgs eventArgs = null;
server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync(o => o.WithCredentials("TheUser"));
await client.DisconnectAsync();
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Disconnected_Event)));
Assert.IsTrue(eventArgs.Endpoint.Contains("::1"));
Assert.AreEqual(MqttClientDisconnectType.Clean, eventArgs.DisconnectType);
}
}
[TestMethod]
public async Task Fire_Client_Subscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientSubscribedTopicEventArgs eventArgs = null;
server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedTopicHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.SubscribeAsync("The/Topic", MqttQualityOfServiceLevel.AtLeastOnce);
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Subscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter.Topic);
Assert.AreEqual(MqttQualityOfServiceLevel.AtLeastOnce, eventArgs.TopicFilter.QualityOfServiceLevel);
}
}
[TestMethod]
public async Task Fire_Client_Unsubscribed_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttServerClientUnsubscribedTopicEventArgs eventArgs = null;
server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.UnsubscribeAsync("The/Topic");
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Client_Unsubscribed_Event)));
Assert.AreEqual("The/Topic", eventArgs.TopicFilter);
}
}
[TestMethod]
public async Task Fire_Application_Message_Received_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
MqttApplicationMessageReceivedEventArgs eventArgs = null;
server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(e =>
{
eventArgs = e;
});
var client = await testEnvironment.ConnectClientAsync();
await client.PublishAsync("The_Topic", "The_Payload");
await LongDelay();
Assert.IsNotNull(eventArgs);
Assert.IsTrue(eventArgs.ClientId.StartsWith(nameof(Fire_Application_Message_Received_Event)));
Assert.AreEqual("The_Topic", eventArgs.ApplicationMessage.Topic);
Assert.AreEqual("The_Payload", eventArgs.ApplicationMessage.ConvertPayloadToString());
}
}
[TestMethod]
public async Task Fire_Started_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = testEnvironment.CreateServer();
EventArgs eventArgs = null;
server.StartedHandler = new MqttServerStartedHandlerDelegate(e =>
{
eventArgs = e;
});
await server.StartAsync(new MqttServerOptionsBuilder().Build());
await LongDelay();
Assert.IsNotNull(eventArgs);
}
}
[TestMethod]
public async Task Fire_Stopped_Event()
{
using (var testEnvironment = CreateTestEnvironment())
{
var server = await testEnvironment.StartServerAsync();
EventArgs eventArgs = null;
server.StoppedHandler = new MqttServerStoppedHandlerDelegate(e =>
{
eventArgs = e;
});
await server.StopAsync();
await LongDelay();
Assert.IsNotNull(eventArgs);
}
}
}
}
@@ -10,10 +10,8 @@ using MQTTnet.Server;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
[TestClass] [TestClass]
public class Server_Status_Tests public sealed class Server_Status_Tests : BaseTestClass
{ {
public TestContext TestContext { get; set; }
[TestMethod] [TestMethod]
public async Task Show_Client_And_Session_Statistics() public async Task Show_Client_And_Session_Statistics()
{ {
@@ -143,7 +141,7 @@ namespace MQTTnet.Tests
// At most once will send one packet to the client and the server will reply // At most once will send one packet to the client and the server will reply
// with an additional ACK packet. // with an additional ACK packet.
await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce); await c1.PublishAsync("a", string.Empty, MqttQualityOfServiceLevel.AtLeastOnce);
await Task.Delay(50); await Task.Delay(250);
var clientStatus = await server.GetClientStatusAsync(); var clientStatus = await server.GetClientStatusAsync();
+42 -5
View File
@@ -147,7 +147,7 @@ namespace MQTTnet.Tests
} }
[TestMethod] [TestMethod]
public async Task Will_Message_Do_Not_Send() public async Task Will_Message_Do_Not_Send_On_Clean_Disconnect()
{ {
using (var testEnvironment = new TestEnvironment(TestContext)) using (var testEnvironment = new TestEnvironment(TestContext))
{ {
@@ -155,7 +155,7 @@ namespace MQTTnet.Tests
await testEnvironment.StartServerAsync(); await testEnvironment.StartServerAsync();
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build(); var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").Build();
var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage); var clientOptions = new MqttClientOptionsBuilder().WithWillMessage(willMessage);
@@ -172,6 +172,38 @@ namespace MQTTnet.Tests
} }
} }
[TestMethod]
public async Task Will_Message_Do_Not_Send_On_Takeover()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var receivedMessagesCount = 0;
await testEnvironment.StartServerAsync();
// C1 will receive the last will!
var c1 = await testEnvironment.ConnectClientAsync();
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);
// C3 will do the connection takeover.
var c3 = await testEnvironment.ConnectClientAsync(clientOptions);
await Task.Delay(1000);
Assert.AreEqual(0, receivedMessagesCount);
}
}
[TestMethod] [TestMethod]
public async Task Will_Message_Send() public async Task Will_Message_Send()
{ {
@@ -571,7 +603,12 @@ namespace MQTTnet.Tests
using (var testEnvironment = new TestEnvironment(TestContext)) using (var testEnvironment = new TestEnvironment(TestContext))
{ {
var server = await testEnvironment.StartServerAsync(); var server = await testEnvironment.StartServerAsync();
server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(e => server.SubscribeAsync(e.ClientId, "topic1"));
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.ConnectClientAsync();
var receivedMessages = new List<MqttApplicationMessage>(); var receivedMessages = new List<MqttApplicationMessage>();
@@ -636,11 +673,11 @@ namespace MQTTnet.Tests
Assert.AreEqual(1, clientConnectedCalled); Assert.AreEqual(1, clientConnectedCalled);
Assert.AreEqual(0, clientDisconnectedCalled); Assert.AreEqual(0, clientDisconnectedCalled);
await Task.Delay(500); await Task.Delay(1000);
await c1.DisconnectAsync(); await c1.DisconnectAsync();
await Task.Delay(500); await Task.Delay(1000);
Assert.AreEqual(1, clientConnectedCalled); Assert.AreEqual(1, clientConnectedCalled);
Assert.AreEqual(1, clientDisconnectedCalled); Assert.AreEqual(1, clientDisconnectedCalled);
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.Tests namespace MQTTnet.Tests
{ {
+5
View File
@@ -31,6 +31,7 @@ namespace MQTTnet.TestApp.NetCore
Console.WriteLine("a = Start QoS 2 benchmark"); Console.WriteLine("a = Start QoS 2 benchmark");
Console.WriteLine("b = Start QoS 1 benchmark"); Console.WriteLine("b = Start QoS 1 benchmark");
Console.WriteLine("c = Start QoS 0 benchmark"); Console.WriteLine("c = Start QoS 0 benchmark");
Console.WriteLine("d = Start server with logging");
var pressedKey = Console.ReadKey(true); var pressedKey = Console.ReadKey(true);
if (pressedKey.KeyChar == '1') if (pressedKey.KeyChar == '1')
@@ -83,6 +84,10 @@ namespace MQTTnet.TestApp.NetCore
{ {
Task.Run(PerformanceTest.RunQoS0Test); Task.Run(PerformanceTest.RunQoS0Test);
} }
else if (pressedKey.KeyChar == 'd')
{
Task.Run(ServerTest.RunEmptyServerWithLogging);
}
Thread.Sleep(Timeout.Infinite); Thread.Sleep(Timeout.Infinite);
} }
@@ -2,8 +2,10 @@
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MQTTnet.Client.Receiving; using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Protocol; using MQTTnet.Protocol;
using MQTTnet.Server; using MQTTnet.Server;
using MQTTnet.Server.Internal;
namespace MQTTnet.TestApp.NetCore namespace MQTTnet.TestApp.NetCore
{ {
@@ -18,6 +20,19 @@ namespace MQTTnet.TestApp.NetCore
Console.ReadLine(); Console.ReadLine();
} }
public static void RunEmptyServerWithLogging()
{
var logger = new MqttNetLogger();
MqttNetConsoleLogger.ForwardToConsole(logger);
var mqttFactory = new MqttFactory(logger);
var mqttServer = mqttFactory.CreateMqttServer();
mqttServer.StartAsync(new MqttServerOptions()).GetAwaiter().GetResult();
Console.WriteLine("Press any key to exit.");
Console.ReadLine();
}
public static async Task RunAsync() public static async Task RunAsync()
{ {
try try