mirror of
https://github.com/dotnet/MQTTnet.git
synced 2026-10-18 10:26:11 +08:00
Merge proces, enable signing of the assembly.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class CheckSubscriptionsResult
|
||||
{
|
||||
public bool IsSubscribed { get; set; }
|
||||
|
||||
public MqttQualityOfServiceLevel QualityOfServiceLevel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class ConnectedMqttClient
|
||||
{
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public MqttProtocolVersion ProtocolVersion { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class GetOrCreateClientSessionResult
|
||||
{
|
||||
public bool IsExistingSession { get; set; }
|
||||
|
||||
public MqttClientSession Session { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttServer : IApplicationMessageReceiver, IApplicationMessagePublisher
|
||||
{
|
||||
event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
event EventHandler<MqttServerStartedEventArgs> Started;
|
||||
|
||||
Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync();
|
||||
|
||||
Task StartAsync(MqttServerOptions options);
|
||||
Task StopAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttServerFactory
|
||||
{
|
||||
IMqttServer CreateMqttServer();
|
||||
|
||||
IMqttServer CreateMqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public interface IMqttServerStorage
|
||||
{
|
||||
Task SaveRetainedMessagesAsync(IList<MqttApplicationMessage> messages);
|
||||
|
||||
Task<IList<MqttApplicationMessage>> LoadRetainedMessagesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttApplicationMessageInterceptorContext
|
||||
{
|
||||
public MqttApplicationMessage ApplicationMessage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientConnectedEventArgs(ConnectedMqttClient client)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public MqttClientDisconnectedEventArgs(ConnectedMqttClient client)
|
||||
{
|
||||
Client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
}
|
||||
|
||||
public ConnectedMqttClient Client { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientPendingMessagesQueue
|
||||
{
|
||||
private readonly BlockingCollection<MqttPublishPacket> _pendingPublishPackets = new BlockingCollection<MqttPublishPacket>();
|
||||
private readonly MqttServerOptions _options;
|
||||
private readonly MqttClientSession _session;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttClientPendingMessagesQueue(MqttServerOptions options, MqttClientSession session, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public void Start(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () => await SendPendingPublishPacketsAsync(adapter, cancellationToken), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Enqueue(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
_pendingPublishPackets.Add(publishPacket);
|
||||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
|
||||
private async Task SendPendingPublishPacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await SendPendingPublishPacketAsync(adapter, cancellationToken);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Unhandled exception while sending enqueued packet (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendPendingPublishPacketAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
MqttPublishPacket packet = null;
|
||||
try
|
||||
{
|
||||
packet = _pendingPublishPackets.Take(cancellationToken);
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, packet).ConfigureAwait(false);
|
||||
|
||||
_logger.Trace<MqttClientPendingMessagesQueue>("Enqueued packet sent (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is MqttCommunicationTimedOutException)
|
||||
{
|
||||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to timeout (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
else if (exception is MqttCommunicationException)
|
||||
{
|
||||
_logger.Warning<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed due to communication exception (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
else if (exception is OperationCanceledException)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Error<MqttClientPendingMessagesQueue>(exception, "Sending publish packet failed (ClientId: {0}).", _session.ClientId);
|
||||
}
|
||||
|
||||
if (packet != null && packet.QualityOfServiceLevel > MqttQualityOfServiceLevel.AtMostOnce)
|
||||
{
|
||||
packet.Dup = true;
|
||||
_pendingPublishPackets.Add(packet, CancellationToken.None);
|
||||
}
|
||||
|
||||
await _session.StopAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSession
|
||||
{
|
||||
private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
|
||||
|
||||
private readonly MqttClientSubscriptionsManager _subscriptionsManager;
|
||||
private readonly MqttClientSessionsManager _sessionsManager;
|
||||
private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
|
||||
private readonly MqttServerOptions _options;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private IMqttChannelAdapter _adapter;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private MqttApplicationMessage _willMessage;
|
||||
|
||||
public MqttClientSession(
|
||||
string clientId,
|
||||
MqttServerOptions options,
|
||||
MqttClientSessionsManager sessionsManager,
|
||||
IMqttNetLogger logger)
|
||||
{
|
||||
_sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
ClientId = clientId;
|
||||
|
||||
_options = options;
|
||||
|
||||
_subscriptionsManager = new MqttClientSubscriptionsManager(_options);
|
||||
_pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, _logger);
|
||||
}
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
|
||||
|
||||
public bool IsConnected => _adapter != null;
|
||||
|
||||
public async Task RunAsync(MqttApplicationMessage willMessage, IMqttChannelAdapter adapter)
|
||||
{
|
||||
if (adapter == null) throw new ArgumentNullException(nameof(adapter));
|
||||
|
||||
try
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_willMessage = willMessage;
|
||||
_adapter = adapter;
|
||||
_cancellationTokenSource = cancellationTokenSource;
|
||||
|
||||
_pendingMessagesQueue.Start(adapter, cancellationTokenSource.Token);
|
||||
await ReceivePacketsAsync(adapter, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource?.Cancel(false);
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
if (_adapter != null)
|
||||
{
|
||||
await _adapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
_adapter = null;
|
||||
}
|
||||
|
||||
_logger.Info<MqttClientSession>("Client '{0}': Session stopped.", ClientId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var willMessage = _willMessage;
|
||||
if (willMessage != null)
|
||||
{
|
||||
_willMessage = null; //clear willmessage so it is send just once
|
||||
await _sessionsManager.DispatchApplicationMessageAsync(this, willMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
var result = _subscriptionsManager.CheckSubscriptions(publishPacket);
|
||||
if (!result.IsSubscribed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
publishPacket.QualityOfServiceLevel = result.QualityOfServiceLevel;
|
||||
_pendingMessagesQueue.Enqueue(publishPacket);
|
||||
}
|
||||
|
||||
private async Task ReceivePacketsAsync(IMqttChannelAdapter adapter, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
|
||||
await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (MqttCommunicationException exception)
|
||||
{
|
||||
_logger.Warning<MqttClientSession>(exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
|
||||
await StopAsync();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSession>(exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
|
||||
await StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task ProcessReceivedPacketAsync(IMqttChannelAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (packet is MqttPingReqPacket)
|
||||
{
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPingRespPacket());
|
||||
}
|
||||
|
||||
if (packet is MqttPublishPacket publishPacket)
|
||||
{
|
||||
return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRelPacket pubRelPacket)
|
||||
{
|
||||
return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttPubRecPacket pubRecPacket)
|
||||
{
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, pubRecPacket.CreateResponse<MqttPubRelPacket>());
|
||||
}
|
||||
|
||||
if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
|
||||
{
|
||||
// Discard message.
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
if (packet is MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken);
|
||||
}
|
||||
|
||||
if (packet is MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, _subscriptionsManager.Unsubscribe(unsubscribePacket));
|
||||
}
|
||||
|
||||
if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
|
||||
{
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
_logger.Warning<MqttClientSession>("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
|
||||
return StopAsync();
|
||||
}
|
||||
|
||||
private async Task HandleIncomingSubscribePacketAsync(IMqttChannelAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket, ClientId);
|
||||
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false);
|
||||
await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false);
|
||||
|
||||
if (subscribeResult.CloseConnection)
|
||||
{
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false);
|
||||
await StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
var retainedMessages = await _sessionsManager.GetRetainedMessagesAsync(subscribePacket).ConfigureAwait(false);
|
||||
foreach (var publishPacket in retainedMessages)
|
||||
{
|
||||
EnqueuePublishPacket(publishPacket.ToPublishPacket());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleIncomingPublishPacketAsync(IMqttChannelAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
var applicationMessage = publishPacket.ToApplicationMessage();
|
||||
|
||||
switch (applicationMessage.QualityOfServiceLevel)
|
||||
{
|
||||
case MqttQualityOfServiceLevel.AtMostOnce:
|
||||
{
|
||||
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage);
|
||||
return;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.AtLeastOnce:
|
||||
{
|
||||
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage);
|
||||
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
|
||||
new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
|
||||
|
||||
return;
|
||||
}
|
||||
case MqttQualityOfServiceLevel.ExactlyOnce:
|
||||
{
|
||||
// QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
|
||||
lock (_unacknowledgedPublishPackets)
|
||||
{
|
||||
_unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
|
||||
}
|
||||
|
||||
await _sessionsManager.DispatchApplicationMessageAsync(this, applicationMessage);
|
||||
|
||||
await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
|
||||
new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new MqttCommunicationException("Received a not supported QoS level.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleIncomingPubRelPacketAsync(IMqttChannelAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
|
||||
{
|
||||
lock (_unacknowledgedPublishPackets)
|
||||
{
|
||||
_unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
|
||||
}
|
||||
|
||||
return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Exceptions;
|
||||
using MQTTnet.Internal;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
using MQTTnet.Serializer;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSessionsManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttClientSession> _sessions = new Dictionary<string, MqttClientSession>();
|
||||
private readonly SemaphoreSlim _sessionsSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly MqttServerOptions _options;
|
||||
private readonly MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
public MqttClientSessionsManager(MqttServerOptions options, MqttRetainedMessagesManager retainedMessagesManager, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_retainedMessagesManager = retainedMessagesManager ?? throw new ArgumentNullException(nameof(retainedMessagesManager));
|
||||
}
|
||||
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
|
||||
public async Task RunClientSessionAsync(IMqttChannelAdapter clientAdapter, CancellationToken cancellationToken)
|
||||
{
|
||||
var clientId = string.Empty;
|
||||
try
|
||||
{
|
||||
if (!(await clientAdapter.ReceivePacketAsync(_options.DefaultCommunicationTimeout, cancellationToken).ConfigureAwait(false) is MqttConnectPacket connectPacket))
|
||||
{
|
||||
throw new MqttProtocolViolationException("The first packet from a client must be a 'CONNECT' packet [MQTT-3.1.0-1].");
|
||||
}
|
||||
|
||||
clientId = connectPacket.ClientId;
|
||||
|
||||
// Switch to the required protocol version before sending any response.
|
||||
clientAdapter.PacketSerializer.ProtocolVersion = connectPacket.ProtocolVersion;
|
||||
|
||||
var connectReturnCode = ValidateConnection(connectPacket);
|
||||
if (connectReturnCode != MqttConnectReturnCode.ConnectionAccepted)
|
||||
{
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var clientSession = await GetOrCreateClientSessionAsync(connectPacket);
|
||||
|
||||
await clientAdapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttConnAckPacket
|
||||
{
|
||||
ConnectReturnCode = connectReturnCode,
|
||||
IsSessionPresent = clientSession.IsExistingSession
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
ClientConnected?.Invoke(this, new MqttClientConnectedEventArgs(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
}));
|
||||
|
||||
await clientSession.Session.RunAsync(connectPacket.WillMessage, clientAdapter).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, exception.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await clientAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
ClientDisconnected?.Invoke(this, new MqttClientDisconnectedEventArgs(new ConnectedMqttClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ProtocolVersion = clientAdapter.PacketSerializer.ProtocolVersion
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
await session.Value.StopAsync();
|
||||
}
|
||||
|
||||
_sessions.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
{
|
||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return _sessions.Where(s => s.Value.IsConnected).Select(s => new ConnectedMqttClient
|
||||
{
|
||||
ClientId = s.Value.ClientId,
|
||||
ProtocolVersion = s.Value.ProtocolVersion ?? MqttProtocolVersion.V311
|
||||
}).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DispatchApplicationMessageAsync(MqttClientSession senderClientSession, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
var interceptorContext = new MqttApplicationMessageInterceptorContext
|
||||
{
|
||||
ApplicationMessage = applicationMessage
|
||||
};
|
||||
|
||||
_options.ApplicationMessageInterceptor?.Invoke(interceptorContext);
|
||||
applicationMessage = interceptorContext.ApplicationMessage;
|
||||
|
||||
if (applicationMessage.Retain)
|
||||
{
|
||||
await _retainedMessagesManager.HandleMessageAsync(senderClientSession?.ClientId, applicationMessage).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var eventArgs = new MqttApplicationMessageReceivedEventArgs(senderClientSession?.ClientId, applicationMessage);
|
||||
ApplicationMessageReceived?.Invoke(this, eventArgs);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttClientSessionsManager>(exception, "Error while processing application message");
|
||||
}
|
||||
|
||||
lock (_sessions)
|
||||
{
|
||||
foreach (var clientSession in _sessions.Values.ToList())
|
||||
{
|
||||
clientSession.EnqueuePublishPacket(applicationMessage.ToPublishPacket());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<MqttApplicationMessage>> GetRetainedMessagesAsync(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
return _retainedMessagesManager.GetSubscribedMessagesAsync(subscribePacket);
|
||||
}
|
||||
|
||||
private MqttConnectReturnCode ValidateConnection(MqttConnectPacket connectPacket)
|
||||
{
|
||||
if (_options.ConnectionValidator != null)
|
||||
{
|
||||
return _options.ConnectionValidator(connectPacket);
|
||||
}
|
||||
|
||||
return MqttConnectReturnCode.ConnectionAccepted;
|
||||
}
|
||||
|
||||
private async Task<GetOrCreateClientSessionResult> GetOrCreateClientSessionAsync(MqttConnectPacket connectPacket)
|
||||
{
|
||||
await _sessionsSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var isSessionPresent = _sessions.TryGetValue(connectPacket.ClientId, out var clientSession);
|
||||
if (isSessionPresent)
|
||||
{
|
||||
if (connectPacket.CleanSession)
|
||||
{
|
||||
_sessions.Remove(connectPacket.ClientId);
|
||||
await clientSession.StopAsync();
|
||||
clientSession = null;
|
||||
|
||||
_logger.Trace<MqttClientSessionsManager>("Stopped existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Trace<MqttClientSessionsManager>("Reusing existing session of client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
}
|
||||
|
||||
var isExistingSession = true;
|
||||
if (clientSession == null)
|
||||
{
|
||||
isExistingSession = false;
|
||||
|
||||
clientSession = new MqttClientSession(connectPacket.ClientId, _options, this, _logger);
|
||||
_sessions[connectPacket.ClientId] = clientSession;
|
||||
|
||||
_logger.Trace<MqttClientSessionsManager>("Created a new session for client '{0}'.", connectPacket.ClientId);
|
||||
}
|
||||
|
||||
return new GetOrCreateClientSessionResult { IsExistingSession = isExistingSession, Session = clientSession };
|
||||
}
|
||||
finally
|
||||
{
|
||||
_sessionsSemaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttClientSubscribeResult
|
||||
{
|
||||
public MqttSubAckPacket ResponsePacket { get; set; }
|
||||
|
||||
public bool CloseConnection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttClientSubscriptionsManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttQualityOfServiceLevel> _subscriptions = new Dictionary<string, MqttQualityOfServiceLevel>();
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
public MqttClientSubscriptionsManager(MqttServerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public MqttClientSubscribeResult Subscribe(MqttSubscribePacket subscribePacket, string clientId)
|
||||
{
|
||||
if (subscribePacket == null) throw new ArgumentNullException(nameof(subscribePacket));
|
||||
|
||||
var responsePacket = subscribePacket.CreateResponse<MqttSubAckPacket>();
|
||||
var closeConnection = false;
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var topicFilter in subscribePacket.TopicFilters)
|
||||
{
|
||||
var interceptorContext = new MqttSubscriptionInterceptorContext(clientId, topicFilter);
|
||||
_options.SubscriptionInterceptor?.Invoke(interceptorContext);
|
||||
responsePacket.SubscribeReturnCodes.Add(interceptorContext.AcceptSubscription ? MqttSubscribeReturnCode.SuccessMaximumQoS1 : MqttSubscribeReturnCode.Failure);
|
||||
|
||||
if (interceptorContext.CloseConnection)
|
||||
{
|
||||
closeConnection = true;
|
||||
}
|
||||
|
||||
if (interceptorContext.AcceptSubscription)
|
||||
{
|
||||
_subscriptions[topicFilter.Topic] = topicFilter.QualityOfServiceLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new MqttClientSubscribeResult
|
||||
{
|
||||
ResponsePacket = responsePacket,
|
||||
CloseConnection = closeConnection
|
||||
};
|
||||
}
|
||||
|
||||
public MqttUnsubAckPacket Unsubscribe(MqttUnsubscribePacket unsubscribePacket)
|
||||
{
|
||||
if (unsubscribePacket == null) throw new ArgumentNullException(nameof(unsubscribePacket));
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var topicFilter in unsubscribePacket.TopicFilters)
|
||||
{
|
||||
_subscriptions.Remove(topicFilter);
|
||||
}
|
||||
}
|
||||
|
||||
return unsubscribePacket.CreateResponse<MqttUnsubAckPacket>();
|
||||
}
|
||||
|
||||
public CheckSubscriptionsResult CheckSubscriptions(MqttPublishPacket publishPacket)
|
||||
{
|
||||
if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
|
||||
|
||||
lock (_subscriptions)
|
||||
{
|
||||
foreach (var subscription in _subscriptions)
|
||||
{
|
||||
if (!MqttTopicFilterComparer.IsMatch(publishPacket.Topic, subscription.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var effectiveQos = subscription.Value;
|
||||
if (publishPacket.QualityOfServiceLevel < effectiveQos)
|
||||
{
|
||||
effectiveQos = publishPacket.QualityOfServiceLevel;
|
||||
}
|
||||
|
||||
return new CheckSubscriptionsResult
|
||||
{
|
||||
IsSubscribed = true,
|
||||
QualityOfServiceLevel = effectiveQos
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new CheckSubscriptionsResult
|
||||
{
|
||||
IsSubscribed = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttConnectionValidatorContext
|
||||
{
|
||||
public MqttConnectionValidatorContext(string clientId, string username, string password, MqttApplicationMessage willMessage)
|
||||
{
|
||||
ClientId = clientId;
|
||||
Username = username;
|
||||
Password = password;
|
||||
WillMessage = willMessage;
|
||||
}
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public string Username { get; }
|
||||
|
||||
public string Password { get; }
|
||||
|
||||
public MqttApplicationMessage WillMessage { get; }
|
||||
|
||||
public MqttConnectReturnCode ReturnCode { get; set; } = MqttConnectReturnCode.ConnectionAccepted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Diagnostics;
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttRetainedMessagesManager
|
||||
{
|
||||
private readonly Dictionary<string, MqttApplicationMessage> _retainedMessages = new Dictionary<string, MqttApplicationMessage>();
|
||||
private readonly SemaphoreSlim _gate = new SemaphoreSlim(1, 1);
|
||||
private readonly IMqttNetLogger _logger;
|
||||
private readonly MqttServerOptions _options;
|
||||
|
||||
public MqttRetainedMessagesManager(MqttServerOptions options, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task LoadMessagesAsync()
|
||||
{
|
||||
if (_options.Storage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
var retainedMessages = await _options.Storage.LoadRetainedMessagesAsync();
|
||||
|
||||
_retainedMessages.Clear();
|
||||
foreach (var retainedMessage in retainedMessages)
|
||||
{
|
||||
_retainedMessages[retainedMessage.Topic] = retainedMessage;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttRetainedMessagesManager>(exception, "Unhandled exception while loading retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleMessageAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
if (applicationMessage == null) throw new ArgumentNullException(nameof(applicationMessage));
|
||||
|
||||
await _gate.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await HandleMessageInternalAsync(clientId, applicationMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.Error<MqttRetainedMessagesManager>(exception, "Unhandled exception while handling retained messages.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<MqttApplicationMessage>> GetSubscribedMessagesAsync(MqttSubscribePacket subscribePacket)
|
||||
{
|
||||
var retainedMessages = new List<MqttApplicationMessage>();
|
||||
|
||||
await _gate.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
foreach (var retainedMessage in _retainedMessages.Values)
|
||||
{
|
||||
foreach (var topicFilter in subscribePacket.TopicFilters)
|
||||
{
|
||||
if (retainedMessage.QualityOfServiceLevel < topicFilter.QualityOfServiceLevel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MqttTopicFilterComparer.IsMatch(retainedMessage.Topic, topicFilter.Topic))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
retainedMessages.Add(retainedMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
|
||||
return retainedMessages;
|
||||
}
|
||||
|
||||
private async Task HandleMessageInternalAsync(string clientId, MqttApplicationMessage applicationMessage)
|
||||
{
|
||||
var saveIsRequired = false;
|
||||
|
||||
if (applicationMessage.Payload?.Any() == false)
|
||||
{
|
||||
saveIsRequired = _retainedMessages.Remove(applicationMessage.Topic);
|
||||
_logger.Info<MqttRetainedMessagesManager>("Client '{0}' cleared retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_retainedMessages.ContainsKey(applicationMessage.Topic))
|
||||
{
|
||||
_retainedMessages[applicationMessage.Topic] = applicationMessage;
|
||||
saveIsRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var existingMessage = _retainedMessages[applicationMessage.Topic];
|
||||
if (existingMessage.QualityOfServiceLevel != applicationMessage.QualityOfServiceLevel || !existingMessage.Payload.SequenceEqual(applicationMessage.Payload ?? new byte[0]))
|
||||
{
|
||||
_retainedMessages[applicationMessage.Topic] = applicationMessage;
|
||||
saveIsRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info<MqttRetainedMessagesManager>("Client '{0}' set retained message for topic '{1}'.", clientId, applicationMessage.Topic);
|
||||
}
|
||||
|
||||
if (!saveIsRequired)
|
||||
{
|
||||
_logger.Trace<MqttRetainedMessagesManager>("Skipped saving retained messages because no changes were detected.");
|
||||
}
|
||||
|
||||
if (saveIsRequired && _options.Storage != null)
|
||||
{
|
||||
await _options.Storage.SaveRetainedMessagesAsync(_retainedMessages.Values.ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MQTTnet.Adapter;
|
||||
using MQTTnet.Diagnostics;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServer : IMqttServer
|
||||
{
|
||||
private readonly ICollection<IMqttServerAdapter> _adapters;
|
||||
private readonly IMqttNetLogger _logger;
|
||||
|
||||
private MqttClientSessionsManager _clientSessionsManager;
|
||||
private MqttRetainedMessagesManager _retainedMessagesManager;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private MqttServerOptions _options;
|
||||
|
||||
public MqttServer(IEnumerable<IMqttServerAdapter> adapters, IMqttNetLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
if (adapters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapters));
|
||||
}
|
||||
|
||||
_adapters = adapters.ToList();
|
||||
}
|
||||
|
||||
public event EventHandler<MqttServerStartedEventArgs> Started;
|
||||
public event EventHandler<MqttClientConnectedEventArgs> ClientConnected;
|
||||
public event EventHandler<MqttClientDisconnectedEventArgs> ClientDisconnected;
|
||||
public event EventHandler<MqttApplicationMessageReceivedEventArgs> ApplicationMessageReceived;
|
||||
|
||||
public Task<IList<ConnectedMqttClient>> GetConnectedClientsAsync()
|
||||
{
|
||||
return _clientSessionsManager.GetConnectedClientsAsync();
|
||||
}
|
||||
|
||||
public async Task PublishAsync(IEnumerable<MqttApplicationMessage> applicationMessages)
|
||||
{
|
||||
if (applicationMessages == null) throw new ArgumentNullException(nameof(applicationMessages));
|
||||
|
||||
if (_cancellationTokenSource == null) throw new InvalidOperationException("The server is not started.");
|
||||
|
||||
foreach (var applicationMessage in applicationMessages)
|
||||
{
|
||||
await _clientSessionsManager.DispatchApplicationMessageAsync(null, applicationMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StartAsync(MqttServerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (_cancellationTokenSource != null) throw new InvalidOperationException("The server is already started.");
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_retainedMessagesManager = new MqttRetainedMessagesManager(_options, _logger);
|
||||
|
||||
_clientSessionsManager = new MqttClientSessionsManager(_options, _retainedMessagesManager, _logger);
|
||||
_clientSessionsManager.ApplicationMessageReceived += OnApplicationMessageReceived;
|
||||
_clientSessionsManager.ClientConnected += OnClientConnected;
|
||||
_clientSessionsManager.ClientDisconnected += OnClientDisconnected;
|
||||
|
||||
await _retainedMessagesManager.LoadMessagesAsync();
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
adapter.ClientAccepted += OnClientAccepted;
|
||||
await adapter.StartAsync(_options);
|
||||
}
|
||||
|
||||
_logger.Info<MqttServer>("Started.");
|
||||
|
||||
Started?.Invoke(this, new MqttServerStartedEventArgs());
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_cancellationTokenSource == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Cancel(false);
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
foreach (var adapter in _adapters)
|
||||
{
|
||||
adapter.ClientAccepted -= OnClientAccepted;
|
||||
await adapter.StopAsync();
|
||||
}
|
||||
|
||||
await _clientSessionsManager.StopAsync();
|
||||
|
||||
_logger.Info<MqttServer>("Stopped.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cancellationTokenSource = null;
|
||||
|
||||
_retainedMessagesManager = null;
|
||||
|
||||
if (_clientSessionsManager != null)
|
||||
{
|
||||
_clientSessionsManager.ApplicationMessageReceived -= OnApplicationMessageReceived;
|
||||
_clientSessionsManager.ClientConnected -= OnClientConnected;
|
||||
_clientSessionsManager.ClientDisconnected -= OnClientDisconnected;
|
||||
}
|
||||
|
||||
_clientSessionsManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClientAccepted(object sender, MqttServerAdapterClientAcceptedEventArgs eventArgs)
|
||||
{
|
||||
eventArgs.SessionTask = Task.Run(async () => await _clientSessionsManager.RunClientSessionAsync(eventArgs.Client, _cancellationTokenSource.Token), _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
private void OnClientConnected(object sender, MqttClientConnectedEventArgs eventArgs)
|
||||
{
|
||||
_logger.Info<MqttServer>("Client '{0}': Connected.", eventArgs.Client.ClientId);
|
||||
ClientConnected?.Invoke(this, eventArgs);
|
||||
}
|
||||
|
||||
private void OnClientDisconnected(object sender, MqttClientDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
_logger.Info<MqttServer>("Client '{0}': Disconnected.", eventArgs.Client.ClientId);
|
||||
ClientDisconnected?.Invoke(this, eventArgs);
|
||||
}
|
||||
|
||||
private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
|
||||
{
|
||||
ApplicationMessageReceived?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttServerDefaultEndpointOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
public int? Port { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerOptions
|
||||
{
|
||||
public MqttServerDefaultEndpointOptions DefaultEndpointOptions { get; } = new MqttServerDefaultEndpointOptions();
|
||||
|
||||
public MqttServerTlsEndpointOptions TlsEndpointOptions { get; } = new MqttServerTlsEndpointOptions();
|
||||
|
||||
public int ConnectionBacklog { get; set; } = 10;
|
||||
|
||||
public TimeSpan DefaultCommunicationTimeout { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
public Func<MqttConnectPacket, MqttConnectReturnCode> ConnectionValidator { get; set; }
|
||||
|
||||
public Action<MqttApplicationMessageInterceptorContext> ApplicationMessageInterceptor { get; set; }
|
||||
|
||||
public Action<MqttSubscriptionInterceptorContext> SubscriptionInterceptor { get; set; }
|
||||
|
||||
public IMqttServerStorage Storage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using MQTTnet.Packets;
|
||||
using MQTTnet.Protocol;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerOptionsBuilder
|
||||
{
|
||||
private readonly MqttServerOptions _options = new MqttServerOptions();
|
||||
|
||||
public MqttServerOptionsBuilder WithConnectionBacklog(int value)
|
||||
{
|
||||
_options.ConnectionBacklog = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDefaultCommunicationTimeout(TimeSpan value)
|
||||
{
|
||||
_options.DefaultCommunicationTimeout = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDefaultEndpointPort(int value)
|
||||
{
|
||||
_options.DefaultEndpointOptions.Port = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithDefaultEndpoint()
|
||||
{
|
||||
_options.DefaultEndpointOptions.IsEnabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithoutDefaultEndpoint()
|
||||
{
|
||||
_options.DefaultEndpointOptions.IsEnabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptedEndpoint()
|
||||
{
|
||||
_options.TlsEndpointOptions.IsEnabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithoutEncryptedEndpoint()
|
||||
{
|
||||
_options.TlsEndpointOptions.IsEnabled = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithEncryptionCertificate(byte[] value)
|
||||
{
|
||||
_options.TlsEndpointOptions.Certificate = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithStorage(IMqttServerStorage value)
|
||||
{
|
||||
_options.Storage = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithConnectionValidator(Func<MqttConnectPacket, MqttConnectReturnCode> value)
|
||||
{
|
||||
_options.ConnectionValidator = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithApplicationMessageInterceptor(Action<MqttApplicationMessageInterceptorContext> value)
|
||||
{
|
||||
_options.ApplicationMessageInterceptor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptionsBuilder WithSubscriptionInterceptor(Action<MqttSubscriptionInterceptorContext> value)
|
||||
{
|
||||
_options.SubscriptionInterceptor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MqttServerOptions Build()
|
||||
{
|
||||
return _options;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public static class MqttServerOptionsExtensions
|
||||
{
|
||||
public static int GetTlsEndpointPort(this MqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (!options.TlsEndpointOptions.Port.HasValue)
|
||||
{
|
||||
return 8883;
|
||||
}
|
||||
|
||||
return options.TlsEndpointOptions.Port.Value;
|
||||
}
|
||||
|
||||
public static int GetDefaultEndpointPort(this MqttServerOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
if (!options.DefaultEndpointOptions.Port.HasValue)
|
||||
{
|
||||
return 1883;
|
||||
}
|
||||
|
||||
return options.DefaultEndpointOptions.Port.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttServerStartedEventArgs : EventArgs
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public sealed class MqttServerTlsEndpointOptions
|
||||
{
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public int? Port { get; set; }
|
||||
|
||||
public byte[] Certificate { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MQTTnet.Packets;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttSubscribeResult
|
||||
{
|
||||
public MqttSubAckPacket ResponsePacket { get; set; }
|
||||
|
||||
public bool CloseConnection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public class MqttSubscriptionInterceptorContext
|
||||
{
|
||||
public MqttSubscriptionInterceptorContext(string clientId, TopicFilter topicFilter)
|
||||
{
|
||||
ClientId = clientId;
|
||||
TopicFilter = topicFilter;
|
||||
}
|
||||
|
||||
public string ClientId { get; }
|
||||
|
||||
public TopicFilter TopicFilter { get; }
|
||||
|
||||
public bool AcceptSubscription { get; set; } = true;
|
||||
|
||||
public bool CloseConnection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace MQTTnet.Server
|
||||
{
|
||||
public static class MqttTopicFilterComparer
|
||||
{
|
||||
private static readonly char[] TopicLevelSeparator = { '/' };
|
||||
|
||||
public static bool IsMatch(string topic, string filter)
|
||||
{
|
||||
if (topic == null) throw new ArgumentNullException(nameof(topic));
|
||||
if (filter == null) throw new ArgumentNullException(nameof(filter));
|
||||
|
||||
if (string.Equals(topic, filter, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var fragmentsTopic = topic.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
var fragmentsFilter = filter.Split(TopicLevelSeparator, StringSplitOptions.None);
|
||||
|
||||
for (var i = 0; i < fragmentsFilter.Length; i++)
|
||||
{
|
||||
switch (fragmentsFilter[i])
|
||||
{
|
||||
case "+": continue;
|
||||
case "#" when i == fragmentsFilter.Length - 1: return true;
|
||||
}
|
||||
|
||||
if (i >= fragmentsTopic.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(fragmentsFilter[i], fragmentsTopic[i], StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fragmentsTopic.Length <= fragmentsFilter.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user