mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-18 18:43:10 +08:00
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:
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user