Feature/mqttv5 subscriptions server (#1246)

* Add support for RetainHandling.

* Add support for No Local.

* Add support for _SubscriptionIdentifier_.

* Add more Unit Tests.

* Update docs.
This commit is contained in:
Christian
2021-09-23 20:30:43 +02:00
committed by GitHub
parent feb53e78c6
commit c756a4092f
83 changed files with 2032 additions and 1237 deletions
@@ -0,0 +1,9 @@
namespace MQTTnet.Server
{
public sealed class GetSubscribedMessagesFilter
{
public bool IsNewSubscription { get; set; }
public MqttTopicFilter TopicFilter { get; set; }
}
}
@@ -15,7 +15,5 @@ namespace MQTTnet.Server
Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage);
Task<IList<MqttApplicationMessage>> GetMessagesAsync();
Task<IList<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<MqttTopicFilter> topicFilters);
}
}
@@ -1,4 +1,5 @@
using MQTTnet.Protocol;
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
@@ -8,6 +9,10 @@ namespace MQTTnet.Server.Internal
public bool IsSubscribed { get; set; }
public bool RetainAsPublished { get; set; }
public List<uint> SubscriptionIdentifiers { get; set; }
/// <summary>
/// Gets or sets the quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
@@ -7,6 +7,7 @@ using MQTTnet.Client;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Diagnostics;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Implementations;
using MQTTnet.Internal;
using MQTTnet.PacketDispatcher;
@@ -22,7 +23,6 @@ namespace MQTTnet.Server.Internal
readonly MqttPacketIdentifierProvider _packetIdentifierProvider = new MqttPacketIdentifierProvider();
readonly MqttPacketDispatcher _packetDispatcher = new MqttPacketDispatcher();
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
readonly MqttClientSessionsManager _sessionsManager;
readonly IMqttChannelAdapter _channelAdapter;
@@ -30,6 +30,7 @@ namespace MQTTnet.Server.Internal
readonly IMqttServerOptions _serverOptions;
readonly string _endpoint;
readonly MqttConnectPacket _connectPacket;
CancellationTokenSource _cancellationToken;
@@ -39,24 +40,22 @@ namespace MQTTnet.Server.Internal
MqttClientSession session,
IMqttServerOptions serverOptions,
MqttClientSessionsManager sessionsManager,
IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger)
{
_serverOptions = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
_channelAdapter = channelAdapter ?? throw new ArgumentNullException(nameof(channelAdapter));
_endpoint = channelAdapter.Endpoint;
Session = session ?? throw new ArgumentNullException(nameof(session));
ConnectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientConnection));
}
public string ClientId => ConnectPacket.ClientId;
public string ClientId => _connectPacket.ClientId;
public string Endpoint => _endpoint;
@@ -64,7 +63,7 @@ namespace MQTTnet.Server.Internal
public bool IsRunning { get; private set; }
public MqttConnectPacket ConnectPacket { get; }
public ushort KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
public bool IsReadingPacket => _channelAdapter.IsReadingPacket;
@@ -116,7 +115,7 @@ namespace MQTTnet.Server.Internal
{
_logger.Info("Client '{0}': Session started.", ClientId);
Session.WillMessage = ConnectPacket.WillMessage;
Session.WillMessage = _connectPacket.WillMessage;
using (var cancellationToken = new CancellationTokenSource())
{
@@ -177,19 +176,19 @@ namespace MQTTnet.Server.Internal
if (packet is MqttPublishPacket publishPacket)
{
await HandleIncomingPublishPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPubRelPacket pubRelPacket)
{
await HandleIncomingPubRelPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttSubscribePacket subscribePacket)
{
await HandleIncomingSubscribePacketAsync(subscribePacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttUnsubscribePacket unsubscribePacket)
{
await HandleIncomingUnsubscribePacketAsync(unsubscribePacket, cancellationToken).ConfigureAwait(false);
await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
}
else if (packet is MqttPingReqPacket)
{
@@ -239,33 +238,23 @@ namespace MQTTnet.Server.Internal
{
while (!cancellationToken.IsCancellationRequested)
{
queuedApplicationMessage = await Session.ApplicationMessagesQueue.DequeueAsync(cancellationToken).ConfigureAwait(false);
if (queuedApplicationMessage == null)
{
return;
}
queuedApplicationMessage = await Session.ApplicationMessagesQueue.Dequeue(cancellationToken).ConfigureAwait(false);
// Also check the cancellation token here because the dequeue is blocking and may take some time.
if (cancellationToken.IsCancellationRequested)
{
return;
}
publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
if (queuedApplicationMessage == null)
{
// The interceptor has decided that the message is not relevant and will be fully ignored.
continue;
}
if (publishPacket.QualityOfServiceLevel > 0)
publishPacket = await CreatePublishPacket(queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
continue;
}
if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce)
@@ -274,24 +263,24 @@ namespace MQTTnet.Server.Internal
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce)
{
using (var awaiter = _packetDispatcher.AddAwaiter<MqttPubAckPacket>(publishPacket.PacketIdentifier))
using (var awaitable = _packetDispatcher.AddAwaitable<MqttPubAckPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
await awaiter.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
await awaitable.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce)
{
using (var awaiter1 = _packetDispatcher.AddAwaiter<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaiter2 = _packetDispatcher.AddAwaiter<MqttPubCompPacket>(publishPacket.PacketIdentifier))
using (var awaitableRec = _packetDispatcher.AddAwaitable<MqttPubRecPacket>(publishPacket.PacketIdentifier))
using (var awaitableComp = _packetDispatcher.AddAwaitable<MqttPubCompPacket>(publishPacket.PacketIdentifier))
{
await SendPacketAsync(publishPacket, cancellationToken).ConfigureAwait(false);
var pubRecPacket = await awaiter1.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRecPacket = await awaitableRec.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
var pubRelPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubRelPacket(pubRecPacket, MqttApplicationMessageReceivedReasonCode.Success);
await SendPacketAsync(pubRelPacket, cancellationToken).ConfigureAwait(false);
await awaiter2.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
await awaitableComp.WaitOneAsync(_serverOptions.DefaultCommunicationTimeout).ConfigureAwait(false);
}
}
@@ -329,29 +318,48 @@ namespace MQTTnet.Server.Internal
}
}
async Task<MqttPublishPacket> CreatePublishPacket(MqttQueuedApplicationMessage queuedApplicationMessage)
{
var publishPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePublishPacket(queuedApplicationMessage.ApplicationMessage);
publishPacket.QualityOfServiceLevel = queuedApplicationMessage.SubscriptionQualityOfServiceLevel;
if (_channelAdapter.PacketFormatterAdapter.ProtocolVersion == MqttProtocolVersion.V500)
{
publishPacket.Properties.SubscriptionIdentifiers = queuedApplicationMessage.SubscriptionIdentifiers;
}
// Set the retain flag to true according to [MQTT-3.3.1-8] and [MQTT-3.3.1-9].
publishPacket.Retain = queuedApplicationMessage.IsRetainedMessage;
publishPacket = await InvokeClientMessageQueueInterceptor(publishPacket, queuedApplicationMessage).ConfigureAwait(false);
if (publishPacket == null)
{
// The interceptor has decided that the message is not relevant and will be fully ignored.
return null;
}
if (publishPacket.QualityOfServiceLevel > 0)
{
publishPacket.PacketIdentifier = _packetIdentifierProvider.GetNextPacketIdentifier();
}
return publishPacket;
}
void StopInternal()
{
_cancellationToken?.Cancel();
}
async Task EnqueueSubscribedRetainedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
{
var retainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var applicationMessage in retainedMessages)
{
Session.EnqueueApplicationMessage(applicationMessage, ClientId, true);
}
}
Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
Task HandleIncomingPubRelPacket(MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
{
var pubCompPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreatePubCompPacket(pubRelPacket, MqttApplicationMessageReceivedReasonCode.Success);
return SendPacketAsync(pubCompPacket, cancellationToken);
}
async Task HandleIncomingSubscribePacketAsync(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
async Task HandleIncomingSubscribePacket(MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
{
var subscribeResult = await Session.SubscriptionsManager.SubscribeAsync(subscribePacket, ConnectPacket).ConfigureAwait(false);
var subscribeResult = await Session.SubscriptionsManager.Subscribe(subscribePacket).ConfigureAwait(false);
var subAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateSubAckPacket(subscribePacket, subscribeResult);
await SendPacketAsync(subAckPacket, cancellationToken).ConfigureAwait(false);
@@ -361,19 +369,22 @@ namespace MQTTnet.Server.Internal
StopInternal();
return;
}
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket.TopicFilters).ConfigureAwait(false);
foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages)
{
Session.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage);
}
}
async Task HandleIncomingUnsubscribePacketAsync(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
async Task HandleIncomingUnsubscribePacket(MqttUnsubscribePacket unsubscribePacket, CancellationToken cancellationToken)
{
var reasonCodes = await Session.SubscriptionsManager.UnsubscribeAsync(unsubscribePacket).ConfigureAwait(false);
var reasonCodes = await Session.SubscriptionsManager.Unsubscribe(unsubscribePacket).ConfigureAwait(false);
var unsubAckPacket = _channelAdapter.PacketFormatterAdapter.DataConverter.CreateUnsubAckPacket(unsubscribePacket, reasonCodes);
await SendPacketAsync(unsubAckPacket, cancellationToken).ConfigureAwait(false);
}
Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
Task HandleIncomingPublishPacket(MqttPublishPacket publishPacket, CancellationToken cancellationToken)
{
HandleTopicAlias(publishPacket);
@@ -1,34 +1,25 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Server.Status;
namespace MQTTnet.Server.Internal
{
public sealed class MqttClientSession
{
readonly IMqttNetScopedLogger _logger;
readonly DateTime _createdTimestamp = DateTime.UtcNow;
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
public MqttClientSession(
string clientId,
IDictionary<object, object> items,
MqttServerEventDispatcher eventDispatcher,
IMqttServerOptions serverOptions,
IMqttRetainedMessagesManager retainedMessagesManager,
IMqttNetLogger logger)
IMqttServerOptions serverOptions,
IMqttRetainedMessagesManager retainedMessagesManager)
{
ClientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
Items = items ?? throw new ArgumentNullException(nameof(items));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, eventDispatcher, serverOptions);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
if (logger == null) throw new ArgumentNullException(nameof(logger));
_logger = logger.CreateScopedLogger(nameof(MqttClientSession));
SubscriptionsManager = new MqttClientSubscriptionsManager(this, serverOptions, eventDispatcher, retainedMessagesManager);
ApplicationMessagesQueue = new MqttClientSessionApplicationMessagesQueue(serverOptions);
}
public string ClientId { get; }
@@ -45,42 +36,7 @@ namespace MQTTnet.Server.Internal
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
/// </summary>
public IDictionary<object, object> Items { get; }
public bool EnqueueApplicationMessage(MqttApplicationMessage applicationMessage, string senderClientId, bool isRetainedApplicationMessage)
{
var checkSubscriptionsResult = SubscriptionsManager.CheckSubscriptions(applicationMessage.Topic, applicationMessage.QualityOfServiceLevel);
if (!checkSubscriptionsResult.IsSubscribed)
{
return false;
}
_logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", ClientId, applicationMessage.Topic);
ApplicationMessagesQueue.Enqueue(applicationMessage, senderClientId, checkSubscriptionsResult.QualityOfServiceLevel, isRetainedApplicationMessage);
return true;
}
public async Task SubscribeAsync(ICollection<MqttTopicFilter> topicFilters)
{
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
await SubscriptionsManager.SubscribeAsync(topicFilters).ConfigureAwait(false);
var matchingRetainedMessages = await _retainedMessagesManager.GetSubscribedMessagesAsync(topicFilters).ConfigureAwait(false);
foreach (var matchingRetainedMessage in matchingRetainedMessages)
{
EnqueueApplicationMessage(matchingRetainedMessage, null, true);
}
}
public Task UnsubscribeAsync(IEnumerable<string> topicFilters)
{
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
return SubscriptionsManager.UnsubscribeAsync(topicFilters);
}
public void FillSessionStatus(MqttSessionStatus status)
{
status.ClientId = ClientId;
@@ -2,7 +2,6 @@
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Internal;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
@@ -18,20 +17,7 @@ namespace MQTTnet.Server.Internal
}
public int Count => _messageQueue.Count;
public void Enqueue(MqttApplicationMessage applicationMessage, string senderClientId, MqttQualityOfServiceLevel qualityOfServiceLevel, bool isRetainedMessage)
{
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
Enqueue(new MqttQueuedApplicationMessage
{
ApplicationMessage = applicationMessage,
SenderClientId = senderClientId,
SubscriptionQualityOfServiceLevel = qualityOfServiceLevel,
IsRetainedMessage = isRetainedMessage
});
}
public void Enqueue(MqttQueuedApplicationMessage queuedApplicationMessage)
{
if (queuedApplicationMessage == null) throw new ArgumentNullException(nameof(queuedApplicationMessage));
@@ -55,7 +41,7 @@ namespace MQTTnet.Server.Internal
}
}
public async Task<MqttQueuedApplicationMessage> DequeueAsync(CancellationToken cancellationToken)
public async Task<MqttQueuedApplicationMessage> Dequeue(CancellationToken cancellationToken)
{
var dequeueResult = await _messageQueue.TryDequeueAsync(cancellationToken).ConfigureAwait(false);
if (!dequeueResult.IsSuccess)
@@ -21,8 +21,8 @@ namespace MQTTnet.Server.Internal
readonly BlockingCollection<MqttPendingApplicationMessage> _messageQueue = new BlockingCollection<MqttPendingApplicationMessage>();
readonly AsyncLock _createConnectionSyncRoot = new AsyncLock();
readonly Dictionary<string, MqttClientConnection> _connections = new Dictionary<string, MqttClientConnection>();
readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
readonly Dictionary<string, MqttClientConnection> _clientConnections = new Dictionary<string, MqttClientConnection>(4096);
readonly Dictionary<string, MqttClientSession> _clientSessions = new Dictionary<string, MqttClientSession>(4096);
readonly IDictionary<object, object> _serverSessionItems = new ConcurrentDictionary<object, object>();
@@ -80,7 +80,7 @@ namespace MQTTnet.Server.Internal
_logger.Warning("Client '{0}': First received packet was no 'CONNECT' packet [MQTT-3.1.0-1].", channelAdapter.Endpoint);
return null;
}
public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken)
{
MqttClientConnection clientConnection = null;
@@ -95,7 +95,7 @@ namespace MQTTnet.Server.Internal
}
MqttConnAckPacket connAckPacket;
var connectionValidatorContext = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);
if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
{
@@ -127,12 +127,12 @@ namespace MQTTnet.Server.Internal
{
if (clientConnection.ClientId != null)
{
// in case it is a takeover _connections already contains the new connection
// in case it is a takeover _clientConnections already contains the new connection
if (!clientConnection.IsTakenOver)
{
lock (_connections)
lock (_clientConnections)
{
_connections.Remove(clientConnection.ClientId);
_clientConnections.Remove(clientConnection.ClientId);
}
if (!_options.EnablePersistentSessions)
@@ -161,10 +161,10 @@ namespace MQTTnet.Server.Internal
public async Task CloseAllConnectionsAsync()
{
List<MqttClientConnection> connections;
lock (_connections)
lock (_clientConnections)
{
connections = _connections.Values.ToList();
_connections.Clear();
connections = _clientConnections.Values.ToList();
_clientConnections.Clear();
}
foreach (var connection in connections)
@@ -175,9 +175,9 @@ namespace MQTTnet.Server.Internal
public List<MqttClientConnection> GetConnections()
{
lock (_connections)
lock (_clientConnections)
{
return _connections.Values.ToList();
return _clientConnections.Values.ToList();
}
}
@@ -185,9 +185,9 @@ namespace MQTTnet.Server.Internal
{
var result = new List<IMqttClientStatus>();
lock (_connections)
lock (_clientConnections)
{
foreach (var connection in _connections.Values)
foreach (var connection in _clientConnections.Values)
{
var clientStatus = new MqttClientStatus(connection);
connection.FillClientStatus(clientStatus);
@@ -207,9 +207,9 @@ namespace MQTTnet.Server.Internal
{
var result = new List<IMqttSessionStatus>();
lock (_sessions)
lock (_clientSessions)
{
foreach (var session in _sessions.Values)
foreach (var session in _clientSessions.Values)
{
var sessionStatus = new MqttSessionStatus(session, this);
session.FillSessionStatus(sessionStatus);
@@ -228,33 +228,52 @@ namespace MQTTnet.Server.Internal
_messageQueue.Add(new MqttPendingApplicationMessage(applicationMessage, sender));
}
public Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
public async Task SubscribeAsync(string clientId, ICollection<MqttTopicFilter> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return GetSession(clientId).SubscribeAsync(topicFilters);
if (topicFilters is null) throw new ArgumentNullException(nameof(topicFilters));
var fakeSubscribePacket = new MqttSubscribePacket
{
TopicFilters = topicFilters.ToList()
};
var clientSession = GetClientSession(clientId);
var subscribeResult = await clientSession.SubscriptionsManager.Subscribe(fakeSubscribePacket).ConfigureAwait(false);
foreach (var retainedApplicationMessage in subscribeResult.RetainedApplicationMessages)
{
clientSession.ApplicationMessagesQueue.Enqueue(retainedApplicationMessage);
}
}
public Task UnsubscribeAsync(string clientId, IEnumerable<string> topicFilters)
public Task UnsubscribeAsync(string clientId, ICollection<string> topicFilters)
{
if (clientId == null) throw new ArgumentNullException(nameof(clientId));
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
return GetSession(clientId).UnsubscribeAsync(topicFilters);
var fakeUnsubscribePacket = new MqttUnsubscribePacket
{
TopicFilters = topicFilters.ToList()
};
return GetClientSession(clientId).SubscriptionsManager.Unsubscribe(fakeUnsubscribePacket);
}
public async Task DeleteSessionAsync(string clientId)
{
MqttClientConnection connection;
lock (_connections)
lock (_clientConnections)
{
_connections.TryGetValue(clientId, out connection);
_clientConnections.TryGetValue(clientId, out connection);
}
lock (_sessions)
lock (_clientSessions)
{
_sessions.Remove(clientId);
_clientSessions.Remove(clientId);
}
if (connection != null)
@@ -272,12 +291,12 @@ namespace MQTTnet.Server.Internal
async Task TryProcessQueuedApplicationMessagesAsync(CancellationToken cancellationToken)
{
//make sure all queued messages are proccessed befor server stops
// Make sure all queued messages are proccessed befor server stops.
while (!cancellationToken.IsCancellationRequested || _messageQueue.Any())
{
try
{
await TryProcessNextQueuedApplicationMessageAsync().ConfigureAwait(false);
await TryProcessNextQueuedApplicationMessage().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -289,14 +308,14 @@ namespace MQTTnet.Server.Internal
}
}
async Task TryProcessNextQueuedApplicationMessageAsync()
async Task TryProcessNextQueuedApplicationMessage()
{
try
{
MqttPendingApplicationMessage queuedApplicationMessage;
MqttPendingApplicationMessage pendingApplicationMessage;
try
{
queuedApplicationMessage = _messageQueue.Take();
pendingApplicationMessage = _messageQueue.Take();
}
catch (ArgumentNullException)
{
@@ -307,9 +326,9 @@ namespace MQTTnet.Server.Internal
return;
}
var clientConnection = queuedApplicationMessage.Sender;
var clientConnection = pendingApplicationMessage.Sender;
var senderClientId = clientConnection?.ClientId ?? _options.ClientId;
var applicationMessage = queuedApplicationMessage.ApplicationMessage;
var applicationMessage = pendingApplicationMessage.ApplicationMessage;
var interceptor = _options.ApplicationMessageInterceptor;
if (interceptor != null)
@@ -343,18 +362,41 @@ namespace MQTTnet.Server.Internal
var deliveryCount = 0;
List<MqttClientSession> sessions;
lock (_sessions)
lock (_clientSessions)
{
sessions = _sessions.Values.ToList();
sessions = _clientSessions.Values.ToList();
}
foreach (var clientSession in sessions)
{
var isSubscribed = clientSession.EnqueueApplicationMessage(applicationMessage, senderClientId, false);
if (isSubscribed)
var checkSubscriptionsResult = clientSession.SubscriptionsManager.CheckSubscriptions(
applicationMessage.Topic,
applicationMessage.QualityOfServiceLevel,
senderClientId);
if (!checkSubscriptionsResult.IsSubscribed)
{
deliveryCount++;
continue;
}
_logger.Verbose("Client '{0}': Queued application message with topic '{1}'.", clientSession.ClientId, applicationMessage.Topic);
var queuedApplicationMessage = new MqttQueuedApplicationMessage
{
ApplicationMessage = applicationMessage,
SubscriptionQualityOfServiceLevel = checkSubscriptionsResult.QualityOfServiceLevel,
SubscriptionIdentifiers = checkSubscriptionsResult.SubscriptionIdentifiers
};
if (checkSubscriptionsResult.RetainAsPublished)
{
// Transfer the original retain state from the publisher.
// This is a MQTTv5 feature.
queuedApplicationMessage.IsRetainedMessage = applicationMessage.Retain;
}
clientSession.ApplicationMessagesQueue.Enqueue(queuedApplicationMessage);
deliveryCount++;
}
if (deliveryCount == 0)
@@ -413,9 +455,9 @@ namespace MQTTnet.Server.Internal
using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false))
{
MqttClientSession session;
lock (_sessions)
lock (_clientSessions)
{
if (!_sessions.TryGetValue(connectPacket.ClientId, out session))
if (!_clientSessions.TryGetValue(connectPacket.ClientId, out session))
{
_logger.Verbose("Created a new session for client '{0}'.", connectPacket.ClientId);
session = CreateSession(connectPacket.ClientId, sessionItems);
@@ -433,17 +475,17 @@ namespace MQTTnet.Server.Internal
}
}
_sessions[connectPacket.ClientId] = session;
_clientSessions[connectPacket.ClientId] = session;
}
MqttClientConnection existingConnection;
lock (_connections)
lock (_clientConnections)
{
_connections.TryGetValue(connectPacket.ClientId, out existingConnection);
_clientConnections.TryGetValue(connectPacket.ClientId, out existingConnection);
connection = CreateConnection(connectPacket, channelAdapter, session);
_connections[connectPacket.ClientId] = connection;
_clientConnections[connectPacket.ClientId] = connection;
}
if (existingConnection != null)
@@ -490,11 +532,11 @@ namespace MQTTnet.Server.Internal
return interceptorContext;
}
MqttClientSession GetSession(string clientId)
MqttClientSession GetClientSession(string clientId)
{
lock (_sessions)
lock (_clientSessions)
{
if (!_sessions.TryGetValue(clientId, out var session))
if (!_clientSessions.TryGetValue(clientId, out var session))
{
throw new InvalidOperationException($"Client session '{clientId}' is unknown.");
}
@@ -511,7 +553,6 @@ namespace MQTTnet.Server.Internal
session,
_options,
this,
_retainedMessagesManager,
_rootLogger);
}
@@ -522,8 +563,7 @@ namespace MQTTnet.Server.Internal
sessionItems,
_eventDispatcher,
_options,
_retainedMessagesManager,
_rootLogger);
_retainedMessagesManager);
}
}
}
@@ -14,34 +14,41 @@ namespace MQTTnet.Server.Internal
// Since writing is done by multiple threads (server API or connection thread), we cannot avoid locking
// completely by swapping references etc.
readonly ReaderWriterLockSlim _subscriptionsLock = new ReaderWriterLockSlim();
readonly Dictionary<string, MqttTopicFilter> _subscriptions = new Dictionary<string, MqttTopicFilter>();
readonly Dictionary<string, Subscription> _subscriptions = new Dictionary<string, Subscription>(4096);
readonly MqttClientSession _clientSession;
readonly IMqttServerOptions _options;
readonly MqttServerEventDispatcher _eventDispatcher;
readonly IMqttRetainedMessagesManager _retainedMessagesManager;
public MqttClientSubscriptionsManager(MqttClientSession clientSession, MqttServerEventDispatcher eventDispatcher, IMqttServerOptions serverOptions)
public MqttClientSubscriptionsManager(
MqttClientSession clientSession,
IMqttServerOptions serverOptions,
MqttServerEventDispatcher eventDispatcher,
IMqttRetainedMessagesManager retainedMessagesManager)
{
_clientSession = clientSession ?? throw new ArgumentNullException(nameof(clientSession));
_options = serverOptions ?? throw new ArgumentNullException(nameof(serverOptions));
_eventDispatcher = eventDispatcher ?? throw new ArgumentNullException(nameof(eventDispatcher));
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
}
public async Task<MqttClientSubscribeResult> SubscribeAsync(MqttSubscribePacket subscribePacket, MqttConnectPacket connectPacket)
public async Task<SubscribeResult> Subscribe(MqttSubscribePacket subscribePacket)
{
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
if (connectPacket == null) throw new ArgumentNullException(nameof(connectPacket));
var result = new MqttClientSubscribeResult();
var retainedApplicationMessages = await _retainedMessagesManager.GetMessagesAsync().ConfigureAwait(false);
var result = new SubscribeResult();
foreach (var originalTopicFilter in subscribePacket.TopicFilters)
// The topic filters are order by its QoS so that the higher QoS will win over a
// lower one.
foreach (var originalTopicFilter in subscribePacket.TopicFilters.OrderByDescending(f => f.QualityOfServiceLevel))
{
var interceptorContext = await InterceptSubscribeAsync(originalTopicFilter).ConfigureAwait(false);
var interceptorContext = await InterceptSubscribe(originalTopicFilter).ConfigureAwait(false);
var finalTopicFilter = interceptorContext?.TopicFilter ?? originalTopicFilter;
var acceptSubscription = interceptorContext?.AcceptSubscription ?? true;
var closeConnection = interceptorContext?.CloseConnection ?? false;
if (finalTopicFilter == null || string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
if (string.IsNullOrEmpty(finalTopicFilter.Topic) || !acceptSubscription)
{
result.ReturnCodes.Add(MqttSubscribeReturnCode.Failure);
result.ReasonCodes.Add(MqttSubscribeReasonCode.UnspecifiedError);
@@ -57,54 +64,22 @@ namespace MQTTnet.Server.Internal
result.CloseConnection = true;
}
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter?.Topic))
if (!acceptSubscription || string.IsNullOrEmpty(finalTopicFilter.Topic))
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions[finalTopicFilter.Topic] = finalTopicFilter;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
var subscription = CreateSubscription(finalTopicFilter, subscribePacket);
await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, finalTopicFilter).ConfigureAwait(false);
FilterRetainedApplicationMessages(retainedApplicationMessages, subscription, result);
}
return result;
}
public async Task SubscribeAsync(IEnumerable<MqttTopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptSubscribeAsync(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptSubscription)
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions[topicFilter.Topic] = topicFilter;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
await _eventDispatcher.SafeNotifyClientSubscribedTopicAsync(_clientSession.ClientId, topicFilter).ConfigureAwait(false);
}
}
public async Task<List<MqttUnsubscribeReasonCode>> UnsubscribeAsync(MqttUnsubscribePacket unsubscribePacket)
public async Task<List<MqttUnsubscribeReasonCode>> Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
{
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
@@ -112,7 +87,7 @@ namespace MQTTnet.Server.Internal
foreach (var topicFilter in unsubscribePacket.TopicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
var interceptorContext = await InterceptUnsubscribe(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
reasonCodes.Add(MqttUnsubscribeReasonCode.ImplementationSpecificError);
@@ -140,52 +115,50 @@ namespace MQTTnet.Server.Internal
return reasonCodes;
}
public async Task UnsubscribeAsync(IEnumerable<string> topicFilters)
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel, string senderClientId)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
foreach (var topicFilter in topicFilters)
{
var interceptorContext = await InterceptUnsubscribeAsync(topicFilter).ConfigureAwait(false);
if (interceptorContext != null && !interceptorContext.AcceptUnsubscription)
{
continue;
}
_subscriptionsLock.EnterWriteLock();
try
{
_subscriptions.Remove(topicFilter);
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
}
}
public CheckSubscriptionsResult CheckSubscriptions(string topic, MqttQualityOfServiceLevel qosLevel)
{
Dictionary<string, MqttTopicFilter> subscriptionsCopy;
List<Subscription> subscriptions;
_subscriptionsLock.EnterReadLock();
try
{
subscriptionsCopy = new Dictionary<string, MqttTopicFilter>(_subscriptions);
subscriptions = _subscriptions.Values.ToList();
}
finally
{
_subscriptionsLock.ExitReadLock();
}
var senderIsReceiver = string.Equals(senderClientId, _clientSession.ClientId);
var qosLevels = new HashSet<MqttQualityOfServiceLevel>();
foreach (var subscription in subscriptionsCopy)
var subscriptionIdentifiers = new HashSet<uint>();
var retainAsPublished = false;
foreach (var subscription in subscriptions)
{
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Key))
if (subscription.NoLocal && senderIsReceiver)
{
// This is a MQTTv5 feature!
continue;
}
if (subscription.RetainAsPublished)
{
// This is a MQTTv5 feature!
retainAsPublished = true;
}
if (!MqttTopicFilterComparer.IsMatch(topic, subscription.Topic))
{
continue;
}
qosLevels.Add(subscription.Value.QualityOfServiceLevel);
qosLevels.Add(subscription.QualityOfServiceLevel);
if (subscription.Identifier > 0)
{
subscriptionIdentifiers.Add(subscription.Identifier);
}
}
if (qosLevels.Count == 0)
@@ -193,7 +166,84 @@ namespace MQTTnet.Server.Internal
return CheckSubscriptionsResult.NotSubscribed;
}
return CreateSubscriptionResult(qosLevel, qosLevels);
return new CheckSubscriptionsResult
{
IsSubscribed = true,
RetainAsPublished = retainAsPublished,
SubscriptionIdentifiers = subscriptionIdentifiers.ToList(),
QualityOfServiceLevel = GetEffectiveQoS(qosLevel, qosLevels)
};
}
Subscription CreateSubscription(MqttTopicFilter topicFilter, MqttSubscribePacket subscribePacket)
{
var subscription = new Subscription
{
Topic = topicFilter.Topic,
NoLocal = topicFilter.NoLocal,
RetainHandling = topicFilter.RetainHandling,
RetainAsPublished = topicFilter.RetainAsPublished,
QualityOfServiceLevel = topicFilter.QualityOfServiceLevel,
Identifier = subscribePacket.Properties?.SubscriptionIdentifier ?? 0
};
_subscriptionsLock.EnterWriteLock();
try
{
subscription.IsNewSubscription = !_subscriptions.ContainsKey(topicFilter.Topic);
_subscriptions[topicFilter.Topic] = subscription;
}
finally
{
_subscriptionsLock.ExitWriteLock();
}
return subscription;
}
static void FilterRetainedApplicationMessages(IList<MqttApplicationMessage> retainedApplicationMessages, Subscription subscription, SubscribeResult subscribeResult)
{
for (var i = retainedApplicationMessages.Count - 1; i >= 0; i--)
{
var retainedApplicationMessage = retainedApplicationMessages[i];
if (retainedApplicationMessage == null)
{
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.DoNotSendOnSubscribe)
{
// This is a MQTT V5+ feature.
continue;
}
if (subscription.RetainHandling == MqttRetainHandling.SendAtSubscribeIfNewSubscriptionOnly && !subscription.IsNewSubscription)
{
// This is a MQTT V5+ feature.
continue;
}
if (!MqttTopicFilterComparer.IsMatch(retainedApplicationMessage.Topic, subscription.Topic))
{
continue;
}
var queuedApplicationMessage = new MqttQueuedApplicationMessage
{
ApplicationMessage = retainedApplicationMessage,
IsRetainedMessage = true,
SubscriptionQualityOfServiceLevel = subscription.QualityOfServiceLevel
};
if (subscription.Identifier > 0)
{
queuedApplicationMessage.SubscriptionIdentifiers = new List<uint> { subscription.Identifier };
}
subscribeResult.RetainedApplicationMessages.Add(queuedApplicationMessage);
retainedApplicationMessages[i] = null;
}
}
static MqttSubscribeReturnCode ConvertToSubscribeReturnCode(MqttQualityOfServiceLevel qualityOfServiceLevel)
@@ -206,7 +256,7 @@ namespace MQTTnet.Server.Internal
return (MqttSubscribeReasonCode)(int)qualityOfServiceLevel;
}
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribeAsync(MqttTopicFilter topicFilter)
async Task<MqttSubscriptionInterceptorContext> InterceptSubscribe(MqttTopicFilter topicFilter)
{
var interceptor = _options.SubscriptionInterceptor;
if (interceptor == null)
@@ -226,7 +276,7 @@ namespace MQTTnet.Server.Internal
return context;
}
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribeAsync(string topicFilter)
async Task<MqttUnsubscriptionInterceptorContext> InterceptUnsubscribe(string topicFilter)
{
var interceptor = _options.UnsubscriptionInterceptor;
if (interceptor == null)
@@ -246,7 +296,7 @@ namespace MQTTnet.Server.Internal
return context;
}
static CheckSubscriptionsResult CreateSubscriptionResult(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
static MqttQualityOfServiceLevel GetEffectiveQoS(MqttQualityOfServiceLevel qosLevel, HashSet<MqttQualityOfServiceLevel> subscribedQoSLevels)
{
MqttQualityOfServiceLevel effectiveQoS;
if (subscribedQoSLevels.Contains(qosLevel))
@@ -262,11 +312,7 @@ namespace MQTTnet.Server.Internal
effectiveQoS = subscribedQoSLevels.Max();
}
return new CheckSubscriptionsResult
{
IsSubscribed = true,
QualityOfServiceLevel = effectiveQoS
};
return effectiveQoS;
}
}
}
@@ -12,11 +12,11 @@ namespace MQTTnet.Server.Internal
public sealed class MqttRetainedMessagesManager : IMqttRetainedMessagesManager
{
readonly AsyncLock _storageAccessLock = new AsyncLock();
readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>();
readonly Dictionary<string, MqttApplicationMessage> _messages = new Dictionary<string, MqttApplicationMessage>(4096);
IMqttNetScopedLogger _logger;
IMqttServerOptions _options;
// TODO: Get rid of the logger here!
public Task Start(IMqttServerOptions options, IMqttNetLogger logger)
{
@@ -116,36 +116,7 @@ namespace MQTTnet.Server.Internal
_logger.Error(exception, "Unhandled exception while handling retained messages.");
}
}
public Task<IList<MqttApplicationMessage>> GetSubscribedMessagesAsync(ICollection<MqttTopicFilter> topicFilters)
{
if (topicFilters == null) throw new ArgumentNullException(nameof(topicFilters));
var matchingRetainedMessages = new List<MqttApplicationMessage>();
List<MqttApplicationMessage> retainedMessages;
lock (_messages)
{
retainedMessages = _messages.Values.ToList();
}
foreach (var retainedMessage in retainedMessages)
{
foreach (var topicFilter in topicFilters)
{
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
{
continue;
}
matchingRetainedMessages.Add(retainedMessage);
break;
}
}
return Task.FromResult((IList<MqttApplicationMessage>)matchingRetainedMessages);
}
public Task<IList<MqttApplicationMessage>> GetMessagesAsync()
{
lock (_messages)
@@ -76,7 +76,7 @@ namespace MQTTnet.Server.Internal
return;
}
if (connection.ConnectPacket.KeepAlivePeriod == 0)
if (connection.KeepAlivePeriod == 0)
{
// The keep alive feature is not used by the current connection.
return;
@@ -92,7 +92,7 @@ namespace MQTTnet.Server.Internal
// Values described here: [MQTT-3.1.2-24].
// If the client sends 5 sec. the server will allow up to 7.5 seconds.
// If the client sends 1 sec. the server will allow up to 1.5 seconds.
var maxDurationWithoutPacket = connection.ConnectPacket.KeepAlivePeriod * 1.5D;
var maxDurationWithoutPacket = connection.KeepAlivePeriod * 1.5D;
var secondsWithoutPackage = (now - connection.Statistics.LastPacketReceivedTimestamp).TotalSeconds;
if (secondsWithoutPackage < maxDurationWithoutPacket)
@@ -0,0 +1,16 @@
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
public sealed class SubscribeResult
{
public List<MqttSubscribeReturnCode> ReturnCodes { get; } = new List<MqttSubscribeReturnCode>(128);
public List<MqttSubscribeReasonCode> ReasonCodes { get; } = new List<MqttSubscribeReasonCode>(128);
public List<MqttQueuedApplicationMessage> RetainedApplicationMessages { get; } = new List<MqttQueuedApplicationMessage>(1024);
public bool CloseConnection { get; set; }
}
}
@@ -0,0 +1,21 @@
using MQTTnet.Protocol;
namespace MQTTnet.Server.Internal
{
public sealed class Subscription
{
public string Topic { get; set; }
public bool NoLocal { get; set; }
public MqttRetainHandling RetainHandling { get; set; }
public bool RetainAsPublished { get; set; }
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
public uint Identifier { get; set; }
public bool IsNewSubscription { get; set; }
}
}
@@ -1,14 +0,0 @@
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
{
public sealed class MqttClientSubscribeResult
{
public List<MqttSubscribeReturnCode> ReturnCodes { get; } = new List<MqttSubscribeReturnCode>();
public List<MqttSubscribeReasonCode> ReasonCodes { get; } = new List<MqttSubscribeReasonCode>();
public bool CloseConnection { get; set; }
}
}
@@ -17,7 +17,7 @@ namespace MQTTnet.Server
public MqttConnectionValidatorContext(MqttConnectPacket connectPacket, IMqttChannelAdapter clientAdapter)
{
_connectPacket = connectPacket;
_connectPacket = connectPacket ?? throw new ArgumentNullException(nameof(connectPacket));
_clientAdapter = clientAdapter ?? throw new ArgumentNullException(nameof(clientAdapter));
}
@@ -35,9 +35,9 @@ namespace MQTTnet.Server
public MqttProtocolVersion ProtocolVersion => _clientAdapter.PacketFormatterAdapter.ProtocolVersion;
public string Username => _connectPacket?.Username;
public string Username => _connectPacket.Username;
public byte[] RawPassword => _connectPacket?.Password;
public byte[] RawPassword => _connectPacket.Password;
public string Password => Encoding.UTF8.GetString(RawPassword ?? PlatformAbstractionLayer.EmptyByteArray);
@@ -45,7 +45,7 @@ namespace MQTTnet.Server
/// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
public MqttApplicationMessage WillMessage => _connectPacket?.WillMessage;
public MqttApplicationMessage WillMessage => _connectPacket.WillMessage;
/// <summary>
/// Gets or sets a value indicating whether clean sessions are used or not.
@@ -55,7 +55,7 @@ namespace MQTTnet.Server
/// It can also connect as a durable client using a persistent connection.
/// In this mode, the broker will store subscription information, and undelivered messages for the client.
/// </summary>
public bool? CleanSession => _connectPacket?.CleanSession;
public bool? CleanSession => _connectPacket.CleanSession;
/// <summary>
/// Gets or sets the keep alive period.
@@ -64,7 +64,7 @@ namespace MQTTnet.Server
/// This message exchange confirms that the connection is open and working.
/// This period is known as the keep alive period.
/// </summary>
public ushort? KeepAlivePeriod => _connectPacket?.KeepAlivePeriod;
public ushort? KeepAlivePeriod => _connectPacket.KeepAlivePeriod;
/// <summary>
/// Gets or sets the user properties.
@@ -73,57 +73,57 @@ namespace MQTTnet.Server
/// The feature is very similar to the HTTP header concept.
/// Hint: MQTT 5 feature only.
/// </summary>
public List<MqttUserProperty> UserProperties => _connectPacket?.Properties?.UserProperties;
public List<MqttUserProperty> UserProperties => _connectPacket.Properties?.UserProperties;
/// <summary>
/// Gets or sets the authentication data.
/// Hint: MQTT 5 feature only.
/// </summary>
public byte[] AuthenticationData => _connectPacket?.Properties?.AuthenticationData;
public byte[] AuthenticationData => _connectPacket.Properties?.AuthenticationData;
/// <summary>
/// Gets or sets the authentication method.
/// Hint: MQTT 5 feature only.
/// </summary>
public string AuthenticationMethod => _connectPacket?.Properties?.AuthenticationMethod;
public string AuthenticationMethod => _connectPacket.Properties?.AuthenticationMethod;
public uint? MaximumPacketSize => _connectPacket?.Properties?.MaximumPacketSize;
public uint? MaximumPacketSize => _connectPacket.Properties?.MaximumPacketSize;
/// <summary>
/// Gets or sets the receive maximum.
/// This gives the maximum length of the receive messages.
/// </summary>
public ushort? ReceiveMaximum => _connectPacket?.Properties?.ReceiveMaximum;
public ushort? ReceiveMaximum => _connectPacket.Properties?.ReceiveMaximum;
/// <summary>
/// Gets or sets the topic alias maximum.
/// This gives the maximum length of the topic alias.
/// </summary>
public ushort? TopicAliasMaximum => _connectPacket?.Properties?.TopicAliasMaximum;
public ushort TopicAliasMaximum => _connectPacket.Properties?.TopicAliasMaximum ?? 0;
/// <summary>
/// Gets the request problem information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? RequestProblemInformation => _connectPacket?.Properties?.RequestProblemInformation;
public bool? RequestProblemInformation => _connectPacket.Properties?.RequestProblemInformation;
/// <summary>
/// Gets the request response information.
/// Hint: MQTT 5 feature only.
/// </summary>
public bool? RequestResponseInformation => _connectPacket?.Properties?.RequestResponseInformation;
public bool? RequestResponseInformation => _connectPacket.Properties?.RequestResponseInformation;
/// <summary>
/// Gets the session expiry interval.
/// The time after a session expires when it's not actively used.
/// </summary>
public uint? SessionExpiryInterval => _connectPacket?.Properties?.SessionExpiryInterval;
public uint? SessionExpiryInterval => _connectPacket.Properties?.SessionExpiryInterval;
/// <summary>
/// Gets or sets the will delay interval.
/// This is the time between the client disconnect and the time the will message will be sent.
/// </summary>
public uint? WillDelayInterval => _connectPacket?.Properties?.WillDelayInterval;
public uint? WillDelayInterval => _connectPacket.Properties?.WillDelayInterval;
/// <summary>
/// Gets or sets a key/value collection that can be used to share data within the scope of this session.
@@ -144,7 +144,7 @@ namespace MQTTnet.Server
/// Gets or sets the reason code. When a MQTTv3 client connects the enum value must be one which is
/// also supported in MQTTv3. Otherwise the connection attempt will fail because not all codes can be
/// converted properly.
/// Hint: MQTT 5 feature only.
/// MQTTv5 only.
/// </summary>
public MqttConnectReasonCode ReasonCode { get; set; } = MqttConnectReasonCode.Success;
@@ -159,12 +159,23 @@ namespace MQTTnet.Server
/// <summary>
/// Gets or sets the response authentication data.
/// Hint: MQTT 5 feature only.
/// MQTTv5 only.
/// </summary>
public byte[] ResponseAuthenticationData { get; set; }
/// <summary>
/// Gets or sets the assigned client identifier.
/// MQTTv5 only.
/// </summary>
public string AssignedClientIdentifier { get; set; }
public string ReasonString { get; set; }
/// <summary>
/// Gets or sets the server reference. This can be used together with i.e. "Server Moved" to send
/// a different server address to the client.
/// MQTTv5 only.
/// </summary>
public string ServerReference { get; set; }
}
}
@@ -1,4 +1,4 @@
using System;
using System.Collections.Generic;
using MQTTnet.Protocol;
namespace MQTTnet.Server
@@ -11,6 +11,10 @@ namespace MQTTnet.Server
public bool IsRetainedMessage { get; set; }
public List<uint> SubscriptionIdentifiers { get; set; }
public bool IsDuplicate { get; set; }
/// <summary>
/// Gets or sets the subscription quality of service level.
/// The Quality of Service (QoS) level is an agreement between the sender of a message and the receiver of a message that defines the guarantee of delivery for a specific message.
@@ -20,14 +24,5 @@ namespace MQTTnet.Server
/// - Exactly once (2): Message gets delivered exactly once (It's ensured that the message only comes once).
/// </summary>
public MqttQualityOfServiceLevel SubscriptionQualityOfServiceLevel { get; set; }
[Obsolete("Use 'SubscriptionQualityOfServiceLevel' instead.")]
public MqttQualityOfServiceLevel QualityOfServiceLevel
{
get => SubscriptionQualityOfServiceLevel;
set => SubscriptionQualityOfServiceLevel = value;
}
public bool IsDuplicate { get; set; }
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ using MQTTnet.Server.Internal;
namespace MQTTnet.Server
{
public class MqttServerOptions : IMqttServerOptions
public sealed class MqttServerOptions : IMqttServerOptions
{
public MqttServerTcpEndpointOptions DefaultEndpointOptions { get; } = new MqttServerTcpEndpointOptions();