using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; using MQTTnet.Core.Adapter; using MQTTnet.Core.Diagnostics; using MQTTnet.Core.Exceptions; using MQTTnet.Core.Internal; using MQTTnet.Core.Packets; using MQTTnet.Core.Protocol; namespace MQTTnet.Core.Server { public sealed class MqttClientSession : IDisposable { private readonly ConcurrentDictionary _pendingIncomingPublications = new ConcurrentDictionary(); private readonly MqttClientSubscriptionsManager _subscriptionsManager = new MqttClientSubscriptionsManager(); private readonly MqttClientMessageQueue _messageQueue; private readonly Action _publishPacketReceivedCallback; private readonly MqttServerOptions _options; private CancellationTokenSource _cancellationTokenSource; private IMqttCommunicationAdapter _adapter; private string _identifier; private MqttApplicationMessage _willApplicationMessage; public MqttClientSession(MqttServerOptions options, Action publishPacketReceivedCallback) { _options = options ?? throw new ArgumentNullException(nameof(options)); _publishPacketReceivedCallback = publishPacketReceivedCallback ?? throw new ArgumentNullException(nameof(publishPacketReceivedCallback)); _messageQueue = new MqttClientMessageQueue(options); } public bool IsConnected => _adapter != null; public async Task RunAsync(string identifier, MqttApplicationMessage willApplicationMessage, IMqttCommunicationAdapter adapter) { if (adapter == null) throw new ArgumentNullException(nameof(adapter)); _willApplicationMessage = willApplicationMessage; try { _identifier = identifier; _adapter = adapter; _cancellationTokenSource = new CancellationTokenSource(); _messageQueue.Start(adapter); while (!_cancellationTokenSource.IsCancellationRequested) { var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero); await HandleIncomingPacketAsync(packet); } } catch (MqttCommunicationException) { } catch (Exception exception) { MqttTrace.Error(nameof(MqttClientSession), exception, $"Client '{_identifier}': Unhandled exception while processing client packets."); } finally { if (willApplicationMessage != null) { _publishPacketReceivedCallback(this, _willApplicationMessage.ToPublishPacket()); } _messageQueue.Stop(); _cancellationTokenSource.Cancel(); _adapter = null; MqttTrace.Information(nameof(MqttClientSession), $"Client '{_identifier}': Disconnected."); } } public void EnqueuePublishPacket(MqttClientSession senderClientSession, MqttPublishPacket publishPacket) { if (senderClientSession == null) throw new ArgumentNullException(nameof(senderClientSession)); if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket)); if (!_subscriptionsManager.IsTopicSubscribed(publishPacket)) { return; } _messageQueue.Enqueue(senderClientSession, publishPacket); MqttTrace.Verbose(nameof(MqttClientSession), $"Client '{_identifier}: Enqueued pending publish packet."); } public void Dispose() { _cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Dispose(); } private Task HandleIncomingPacketAsync(MqttBasePacket packet) { var subscribePacket = packet as MqttSubscribePacket; if (subscribePacket != null) { return _adapter.SendPacketAsync(_subscriptionsManager.Subscribe(subscribePacket), _options.DefaultCommunicationTimeout); } var unsubscribePacket = packet as MqttUnsubscribePacket; if (unsubscribePacket != null) { return _adapter.SendPacketAsync(_subscriptionsManager.Unsubscribe(unsubscribePacket), _options.DefaultCommunicationTimeout); } var publishPacket = packet as MqttPublishPacket; if (publishPacket != null) { return HandleIncomingPublishPacketAsync(publishPacket); } var pubRelPacket = packet as MqttPubRelPacket; if (pubRelPacket != null) { return HandleIncomingPubRelPacketAsync(pubRelPacket); } var pubAckPacket = packet as MqttPubAckPacket; if (pubAckPacket != null) { return HandleIncomingPubAckPacketAsync(pubAckPacket); } if (packet is MqttPingReqPacket) { return _adapter.SendPacketAsync(new MqttPingRespPacket(), _options.DefaultCommunicationTimeout); } if (packet is MqttDisconnectPacket || packet is MqttConnectPacket) { _cancellationTokenSource.Cancel(); return Task.FromResult((object)null); } MqttTrace.Warning(nameof(MqttClientSession), $"Client '{_identifier}': Received not supported packet ({packet}). Closing connection."); _cancellationTokenSource.Cancel(); return Task.FromResult((object)null); } private async Task HandleIncomingPubAckPacketAsync(MqttPubAckPacket pubAckPacket) { await Task.FromResult((object)null); } private async Task HandleIncomingPublishPacketAsync(MqttPublishPacket publishPacket) { if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtMostOnce) { _publishPacketReceivedCallback(this, publishPacket); } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.AtLeastOnce) { await _adapter.SendPacketAsync(new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout); _publishPacketReceivedCallback(this, publishPacket); } else if (publishPacket.QualityOfServiceLevel == MqttQualityOfServiceLevel.ExactlyOnce) { _pendingIncomingPublications[publishPacket.PacketIdentifier] = publishPacket; await _adapter.SendPacketAsync(new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout); } } private async Task HandleIncomingPubRelPacketAsync(MqttPubRelPacket pubRelPacket) { MqttPublishPacket publishPacket; if (!_pendingIncomingPublications.TryRemove(pubRelPacket.PacketIdentifier, out publishPacket)) { return; } await _adapter.SendPacketAsync(new MqttPubCompPacket { PacketIdentifier = publishPacket.PacketIdentifier }, _options.DefaultCommunicationTimeout); _publishPacketReceivedCallback(this, publishPacket); } } }