You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

280 lines
12 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using MQTTnet.Core.Adapter;
  6. using MQTTnet.Core.Exceptions;
  7. using MQTTnet.Core.Internal;
  8. using MQTTnet.Core.Packets;
  9. using MQTTnet.Core.Protocol;
  10. using MQTTnet.Core.Serializer;
  11. using Microsoft.Extensions.Logging;
  12. using Microsoft.Extensions.Options;
  13. namespace MQTTnet.Core.Server
  14. {
  15. public sealed class MqttClientSession
  16. {
  17. private readonly HashSet<ushort> _unacknowledgedPublishPackets = new HashSet<ushort>();
  18. private readonly IMqttClientRetainedMessageManager _clientRetainedMessageManager;
  19. private readonly MqttClientSubscriptionsManager _subscriptionsManager;
  20. private readonly MqttClientSessionsManager _sessionsManager;
  21. private readonly MqttClientPendingMessagesQueue _pendingMessagesQueue;
  22. private readonly MqttServerOptions _options;
  23. private readonly ILogger<MqttClientSession> _logger;
  24. private IMqttCommunicationAdapter _adapter;
  25. private CancellationTokenSource _cancellationTokenSource;
  26. private MqttApplicationMessage _willMessage;
  27. public MqttClientSession(
  28. string clientId,
  29. IOptions<MqttServerOptions> options,
  30. MqttClientSessionsManager sessionsManager,
  31. MqttClientSubscriptionsManager subscriptionsManager,
  32. ILogger<MqttClientSession> logger,
  33. ILogger<MqttClientPendingMessagesQueue> messageQueueLogger,
  34. IMqttClientRetainedMessageManager clientRetainedMessageManager)
  35. {
  36. _clientRetainedMessageManager = clientRetainedMessageManager ?? throw new ArgumentNullException(nameof(clientRetainedMessageManager));
  37. _sessionsManager = sessionsManager ?? throw new ArgumentNullException(nameof(sessionsManager));
  38. _subscriptionsManager = subscriptionsManager ?? throw new ArgumentNullException(nameof(subscriptionsManager));
  39. _logger = logger ?? throw new ArgumentNullException(nameof(logger));
  40. ClientId = clientId;
  41. _options = options.Value;
  42. _pendingMessagesQueue = new MqttClientPendingMessagesQueue(_options, this, messageQueueLogger);
  43. }
  44. public string ClientId { get; }
  45. public MqttProtocolVersion? ProtocolVersion => _adapter?.PacketSerializer.ProtocolVersion;
  46. public bool IsConnected => _adapter != null;
  47. public async Task RunAsync(MqttApplicationMessage willMessage, IMqttCommunicationAdapter adapter)
  48. {
  49. if (adapter == null) throw new ArgumentNullException(nameof(adapter));
  50. try
  51. {
  52. _willMessage = willMessage;
  53. _adapter = adapter;
  54. _cancellationTokenSource = new CancellationTokenSource();
  55. _pendingMessagesQueue.Start(adapter, _cancellationTokenSource.Token);
  56. await ReceivePacketsAsync(adapter, _cancellationTokenSource.Token).ConfigureAwait(false);
  57. }
  58. catch (OperationCanceledException)
  59. {
  60. }
  61. catch (MqttCommunicationException exception)
  62. {
  63. _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
  64. }
  65. catch (Exception exception)
  66. {
  67. _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
  68. }
  69. }
  70. public void Stop()
  71. {
  72. try
  73. {
  74. _cancellationTokenSource?.Cancel(false);
  75. _cancellationTokenSource?.Dispose();
  76. _cancellationTokenSource = null;
  77. _adapter = null;
  78. _logger.LogInformation("Client '{0}': Disconnected.", ClientId);
  79. }
  80. finally
  81. {
  82. var willMessage = _willMessage;
  83. if (willMessage != null)
  84. {
  85. _willMessage = null; //clear willmessage so it is send just once
  86. _sessionsManager.DispatchApplicationMessage(this, willMessage);
  87. }
  88. }
  89. }
  90. public void EnqueuePublishPacket(MqttPublishPacket publishPacket)
  91. {
  92. if (publishPacket == null) throw new ArgumentNullException(nameof(publishPacket));
  93. if (!_subscriptionsManager.IsSubscribed(publishPacket))
  94. {
  95. return;
  96. }
  97. _pendingMessagesQueue.Enqueue(publishPacket);
  98. }
  99. private async Task ReceivePacketsAsync(IMqttCommunicationAdapter adapter, CancellationToken cancellationToken)
  100. {
  101. try
  102. {
  103. while (!cancellationToken.IsCancellationRequested)
  104. {
  105. var packet = await adapter.ReceivePacketAsync(TimeSpan.Zero, cancellationToken).ConfigureAwait(false);
  106. await ProcessReceivedPacketAsync(adapter, packet, cancellationToken).ConfigureAwait(false);
  107. }
  108. }
  109. catch (OperationCanceledException)
  110. {
  111. }
  112. catch (MqttCommunicationException exception)
  113. {
  114. _logger.LogWarning(new EventId(), exception, "Client '{0}': Communication exception while processing client packets.", ClientId);
  115. Stop();
  116. }
  117. catch (Exception exception)
  118. {
  119. _logger.LogError(new EventId(), exception, "Client '{0}': Unhandled exception while processing client packets.", ClientId);
  120. Stop();
  121. }
  122. }
  123. private Task ProcessReceivedPacketAsync(IMqttCommunicationAdapter adapter, MqttBasePacket packet, CancellationToken cancellationToken)
  124. {
  125. if (packet is MqttPingReqPacket)
  126. {
  127. return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPingRespPacket());
  128. }
  129. if (packet is MqttPublishPacket publishPacket)
  130. {
  131. return HandleIncomingPublishPacketAsync(adapter, publishPacket, cancellationToken);
  132. }
  133. if (packet is MqttPubRelPacket pubRelPacket)
  134. {
  135. return HandleIncomingPubRelPacketAsync(adapter, pubRelPacket, cancellationToken);
  136. }
  137. if (packet is MqttPubRecPacket pubRecPacket)
  138. {
  139. return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, pubRecPacket.CreateResponse<MqttPubRelPacket>());
  140. }
  141. if (packet is MqttPubAckPacket || packet is MqttPubCompPacket)
  142. {
  143. // Discard message.
  144. return Task.FromResult(0);
  145. }
  146. if (packet is MqttSubscribePacket subscribePacket)
  147. {
  148. return HandleIncomingSubscribePacketAsync(adapter, subscribePacket, cancellationToken);
  149. }
  150. if (packet is MqttUnsubscribePacket unsubscribePacket)
  151. {
  152. return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, _subscriptionsManager.Unsubscribe(unsubscribePacket));
  153. }
  154. if (packet is MqttDisconnectPacket || packet is MqttConnectPacket)
  155. {
  156. Stop();
  157. return Task.FromResult(0);
  158. }
  159. _logger.LogWarning("Client '{0}': Received not supported packet ({1}). Closing connection.", ClientId, packet);
  160. Stop();
  161. return Task.FromResult(0);
  162. }
  163. private async Task HandleIncomingSubscribePacketAsync(IMqttCommunicationAdapter adapter, MqttSubscribePacket subscribePacket, CancellationToken cancellationToken)
  164. {
  165. var subscribeResult = _subscriptionsManager.Subscribe(subscribePacket, ClientId);
  166. await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, subscribeResult.ResponsePacket).ConfigureAwait(false);
  167. await EnqueueSubscribedRetainedMessagesAsync(subscribePacket).ConfigureAwait(false);
  168. if (subscribeResult.CloseConnection)
  169. {
  170. await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttDisconnectPacket()).ConfigureAwait(false);
  171. Stop();
  172. }
  173. }
  174. private async Task EnqueueSubscribedRetainedMessagesAsync(MqttSubscribePacket subscribePacket)
  175. {
  176. var retainedMessages = await _clientRetainedMessageManager.GetSubscribedMessagesAsync(subscribePacket).ConfigureAwait(false);
  177. foreach (var publishPacket in retainedMessages)
  178. {
  179. EnqueuePublishPacket(publishPacket.ToPublishPacket());
  180. }
  181. }
  182. private async Task HandleIncomingPublishPacketAsync(IMqttCommunicationAdapter adapter, MqttPublishPacket publishPacket, CancellationToken cancellationToken)
  183. {
  184. var applicationMessage = publishPacket.ToApplicationMessage();
  185. var interceptorContext = new MqttApplicationMessageInterceptorContext
  186. {
  187. ApplicationMessage = applicationMessage
  188. };
  189. _options.ApplicationMessageInterceptor?.Invoke(interceptorContext);
  190. applicationMessage = interceptorContext.ApplicationMessage;
  191. if (applicationMessage.Retain)
  192. {
  193. await _clientRetainedMessageManager.HandleMessageAsync(ClientId, applicationMessage).ConfigureAwait(false);
  194. }
  195. switch (applicationMessage.QualityOfServiceLevel)
  196. {
  197. case MqttQualityOfServiceLevel.AtMostOnce:
  198. {
  199. _sessionsManager.DispatchApplicationMessage(this, applicationMessage);
  200. return;
  201. }
  202. case MqttQualityOfServiceLevel.AtLeastOnce:
  203. {
  204. _sessionsManager.DispatchApplicationMessage(this, applicationMessage);
  205. await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
  206. new MqttPubAckPacket { PacketIdentifier = publishPacket.PacketIdentifier });
  207. return;
  208. }
  209. case MqttQualityOfServiceLevel.ExactlyOnce:
  210. {
  211. // QoS 2 is implement as method "B" [4.3.3 QoS 2: Exactly once delivery]
  212. lock (_unacknowledgedPublishPackets)
  213. {
  214. _unacknowledgedPublishPackets.Add(publishPacket.PacketIdentifier);
  215. }
  216. _sessionsManager.DispatchApplicationMessage(this, applicationMessage);
  217. await adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken,
  218. new MqttPubRecPacket { PacketIdentifier = publishPacket.PacketIdentifier }).ConfigureAwait(false);
  219. return;
  220. }
  221. default:
  222. throw new MqttCommunicationException("Received a not supported QoS level.");
  223. }
  224. }
  225. private Task HandleIncomingPubRelPacketAsync(IMqttCommunicationAdapter adapter, MqttPubRelPacket pubRelPacket, CancellationToken cancellationToken)
  226. {
  227. lock (_unacknowledgedPublishPackets)
  228. {
  229. _unacknowledgedPublishPackets.Remove(pubRelPacket.PacketIdentifier);
  230. }
  231. return adapter.SendPacketsAsync(_options.DefaultCommunicationTimeout, cancellationToken, new MqttPubCompPacket { PacketIdentifier = pubRelPacket.PacketIdentifier });
  232. }
  233. }
  234. }