async Task <MqttClient> CreateClientConnection( MqttConnectPacket connectPacket, MqttConnAckPacket connAckPacket, IMqttChannelAdapter channelAdapter, ValidatingConnectionEventArgs validatingConnectionEventArgs) { MqttClient connection; bool sessionShouldPersist; if (validatingConnectionEventArgs.ProtocolVersion == MqttProtocolVersion.V500) { // MQTT 5.0 section 3.1.2.11.2 // The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 [MQTT-3.1.2-23]. // // A Client that only wants to process messages while connected will set the Clean Start to 1 and set the Session Expiry Interval to 0. // It will not receive Application Messages published before it connected and has to subscribe afresh to any topics that it is interested // in each time it connects. // Persist if SessionExpiryInterval != 0, but may start with a clean session sessionShouldPersist = validatingConnectionEventArgs.SessionExpiryInterval != 0; } else { // MQTT 3.1.1 section 3.1.2.4: persist only if 'not CleanSession' // // If CleanSession is set to 1, the Client and Server MUST discard any previous Session and start a new one. // This Session lasts as long as the Network Connection. State data associated with this Session MUST NOT be // reused in any subsequent Session [MQTT-3.1.2-6]. sessionShouldPersist = !connectPacket.CleanSession; } using (await _createConnectionSyncRoot.WaitAsync(CancellationToken.None).ConfigureAwait(false)) { MqttSession session; lock (_sessionsManagementLock) { if (!_sessions.TryGetValue(connectPacket.ClientId, out session)) { session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist); } else { if (connectPacket.CleanSession) { _logger.Verbose("Deleting existing session of client '{0}'.", connectPacket.ClientId); session = CreateSession(connectPacket.ClientId, validatingConnectionEventArgs.SessionItems, sessionShouldPersist); } else { _logger.Verbose("Reusing existing session of client '{0}'.", connectPacket.ClientId); // Session persistence could change for MQTT 5 clients that reconnect with different SessionExpiryInterval session.IsPersistent = sessionShouldPersist; connAckPacket.IsSessionPresent = true; session.Recover(); } } _sessions[connectPacket.ClientId] = session; } if (!connAckPacket.IsSessionPresent) { // TODO: This event is not yet final. It can already be used but restoring sessions from storage will be added later! var preparingSessionEventArgs = new PreparingSessionEventArgs(); await _eventContainer.PreparingSessionEvent.InvokeAsync(preparingSessionEventArgs).ConfigureAwait(false); } MqttClient existing; lock (_clients) { _clients.TryGetValue(connectPacket.ClientId, out existing); connection = CreateConnection(connectPacket, channelAdapter, session); _clients[connectPacket.ClientId] = connection; } if (existing != null) { existing.IsTakenOver = true; await existing.StopAsync(MqttDisconnectReasonCode.SessionTakenOver).ConfigureAwait(false); if (_eventContainer.ClientConnectedEvent.HasHandlers) { var eventArgs = new ClientDisconnectedEventArgs { ClientId = existing.Id, DisconnectType = MqttClientDisconnectType.Takeover, Endpoint = existing.Endpoint }; await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } } return(connection); }
public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter, CancellationToken cancellationToken) { MqttClient client = null; try { var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false); if (connectPacket == null) { // Nothing was received in time etc. return; } var validatingConnectionEventArgs = await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false); var connAckPacket = _packetFactories.ConnAck.Create(validatingConnectionEventArgs); if (validatingConnectionEventArgs.ReasonCode != MqttConnectReasonCode.Success) { // Send failure response here without preparing a connection and session! await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); return; } // Pass connAckPacket so that IsSessionPresent flag can be set if the client session already exists. client = await CreateClientConnection(connectPacket, connAckPacket, channelAdapter, validatingConnectionEventArgs).ConfigureAwait(false); await client.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false); if (_eventContainer.ClientConnectedEvent.HasHandlers) { var eventArgs = new ClientConnectedEventArgs { ClientId = connectPacket.ClientId, UserName = connectPacket.Username, ProtocolVersion = channelAdapter.PacketFormatterAdapter.ProtocolVersion, Endpoint = channelAdapter.Endpoint }; await _eventContainer.ClientConnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } await client.RunAsync().ConfigureAwait(false); } catch (OperationCanceledException) { } catch (Exception exception) { _logger.Error(exception, exception.Message); } finally { if (client != null) { if (client.Id != null) { // in case it is a takeover _clientConnections already contains the new connection if (!client.IsTakenOver) { lock (_clients) { _clients.Remove(client.Id); } if (!_options.EnablePersistentSessions || !client.Session.IsPersistent) { await DeleteSessionAsync(client.Id).ConfigureAwait(false); } } } var endpoint = client.Endpoint; if (client.Id != null && !client.IsTakenOver && _eventContainer.ClientDisconnectedEvent.HasHandlers) { var eventArgs = new ClientDisconnectedEventArgs { ClientId = client.Id, DisconnectType = client.IsCleanDisconnect ? MqttClientDisconnectType.Clean : MqttClientDisconnectType.NotClean, Endpoint = endpoint }; await _eventContainer.ClientDisconnectedEvent.InvokeAsync(eventArgs).ConfigureAwait(false); } } using (var timeout = new CancellationTokenSource(_options.DefaultCommunicationTimeout)) { await channelAdapter.DisconnectAsync(timeout.Token).ConfigureAwait(false); } } }