예제 #1
0
 public static void RunInBackground(this Task task, MqttNetSourceLogger logger = null)
 {
     task?.ContinueWith(t =>
     {
         // Consume the exception first so that we get no exception regarding the not observed exception.
         var exception = t.Exception;
         logger?.Error(exception, "Unhandled exception in background task.");
     },
                        TaskContinuationOptions.OnlyOnFaulted);
 }
        void InspectPacket(byte[] buffer, MqttPacketFlowDirection direction)
        {
            try
            {
                var eventArgs = new InspectMqttPacketEventArgs
                {
                    Buffer    = buffer,
                    Direction = direction
                };

                _asyncEvent.InvokeAsync(eventArgs).GetAwaiter().GetResult();
            }
            catch (Exception exception)
            {
                _logger.Error(exception, "Error while inspecting packet.");
            }
        }
예제 #3
0
        void InspectPacket(byte[] buffer, MqttPacketFlowDirection direction)
        {
            try
            {
                var context = new ProcessMqttPacketContext
                {
                    Buffer    = buffer,
                    Direction = direction
                };

                _packetInspector.ProcessMqttPacket(context);
            }
            catch (Exception exception)
            {
                _logger.Error(exception, "Error while inspecting packet.");
            }
        }
예제 #4
0
        public async Task HandleClientConnectionAsync(IMqttChannelAdapter channelAdapter,
                                                      CancellationToken cancellationToken)
        {
            MqttClientConnection clientConnection = null;

            try
            {
                var connectPacket = await ReceiveConnectPacket(channelAdapter, cancellationToken).ConfigureAwait(false);

                if (connectPacket == null)
                {
                    // Nothing was received in time etc.
                    return;
                }

                MqttConnAckPacket connAckPacket;

                var connectionValidatorContext =
                    await ValidateConnection(connectPacket, channelAdapter).ConfigureAwait(false);

                if (connectionValidatorContext.ReasonCode != MqttConnectReasonCode.Success)
                {
                    // Send failure response here without preparing a session!
                    connAckPacket =
                        channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(
                            connectionValidatorContext);
                    await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);

                    return;
                }

                connAckPacket =
                    channelAdapter.PacketFormatterAdapter.DataConverter.CreateConnAckPacket(connectionValidatorContext);

                // Pass connAckPacket so that IsSessionPresent flag can be set if the client session already exists
                clientConnection = await CreateClientConnection(connectPacket, connAckPacket, channelAdapter,
                                                                connectionValidatorContext.SessionItems).ConfigureAwait(false);

                await channelAdapter.SendPacketAsync(connAckPacket, cancellationToken).ConfigureAwait(false);

                await _eventDispatcher.SafeNotifyClientConnectedAsync(connectPacket, channelAdapter)
                .ConfigureAwait(false);

                await clientConnection.RunAsync().ConfigureAwait(false);
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception exception)
            {
                _logger.Error(exception, exception.Message);
            }
            finally
            {
                if (clientConnection != null)
                {
                    if (clientConnection.ClientId != null)
                    {
                        // in case it is a takeover _clientConnections already contains the new connection
                        if (!clientConnection.IsTakenOver)
                        {
                            lock (_clientConnections)
                            {
                                _clientConnections.Remove(clientConnection.ClientId);
                            }

                            if (!_options.EnablePersistentSessions)
                            {
                                await DeleteSessionAsync(clientConnection.ClientId).ConfigureAwait(false);
                            }
                        }
                    }

                    var endpoint = clientConnection.Endpoint;

                    if (clientConnection.ClientId != null && !clientConnection.IsTakenOver)
                    {
                        // The event is fired at a separate place in case of a handover!
                        await _eventDispatcher.SafeNotifyClientDisconnectedAsync(
                            clientConnection.ClientId,
                            clientConnection.IsCleanDisconnect
                            ?MqttClientDisconnectType.Clean
                            : MqttClientDisconnectType.NotClean,
                            endpoint).ConfigureAwait(false);
                    }
                }

                clientConnection?.Dispose();

                await channelAdapter.DisconnectAsync(_options.DefaultCommunicationTimeout, CancellationToken.None)
                .ConfigureAwait(false);
            }
        }
예제 #5
0
        async Task ReceivePackagesLoop(CancellationToken cancellationToken)
        {
            try
            {
                // We do not listen for the cancellation token here because the internal buffer might still
                // contain data to be read even if the TCP connection was already dropped. So we rely on an
                // own exception in the reading loop!
                while (!cancellationToken.IsCancellationRequested)
                {
                    var packet = await _channelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);

                    if (packet == null)
                    {
                        // The client has closed the connection gracefully.
                        return;
                    }

                    Statistics.HandleReceivedPacket(packet);

                    if (packet is MqttPublishPacket publishPacket)
                    {
                        await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttPubRelPacket pubRelPacket)
                    {
                        await HandleIncomingPubRelPacket(pubRelPacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttSubscribePacket subscribePacket)
                    {
                        await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttUnsubscribePacket unsubscribePacket)
                    {
                        await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttPingReqPacket)
                    {
                        // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
                        await SendPacketAsync(MqttPingRespPacket.Instance, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttPingRespPacket)
                    {
                        throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
                    }
                    else if (packet is MqttDisconnectPacket)
                    {
                        IsCleanDisconnect = true;
                        return;
                    }
                    else
                    {
                        if (!_packetDispatcher.TryDispatch(packet))
                        {
                            throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
                        }
                    }
                }
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception exception)
            {
                if (exception is MqttCommunicationException)
                {
                    _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", ClientId);
                }
                else
                {
                    _logger.Error(exception, "Client '{0}': Error while receiving client packets.", ClientId);
                }
            }
        }
예제 #6
0
        /// <inheritdoc/>
        /// <exception cref="ArgumentException"></exception>
        public IObservable <MqttApplicationMessageReceivedEventArgs> Connect(string topic)
        {
            if (string.IsNullOrWhiteSpace(topic))
            {
                throw new ArgumentException($"'{nameof(topic)}' cannot be null or whitespace", nameof(topic));
            }

            ThrowIfDisposed();
            lock (topicSubscriptionCache)
            {
                // try get exiting observable for topic
                if (!topicSubscriptionCache.TryGetValue(topic, out IObservable <MqttApplicationMessageReceivedEventArgs> observable))
                {
                    // create new observable for topic
                    observable = Observable
                                 .Create <MqttApplicationMessageReceivedEventArgs>(async observer =>
                    {
                        // subscribe to topic
                        try
                        {
                            await InternalClient.SubscribeAsync(topic).ConfigureAwait(false);
                        }
                        catch (Exception exception)
                        {
                            logger.Error(exception, "Error while maintaining subscribe from topic.");
                            observer.OnError(exception);
                            return(Disposable.Empty);
                        }

                        // filter all received messages
                        // and subscribe to messages for this topic
                        var messageSubscription = applicationMessageReceived
                                                  .FilterTopic(topic)
                                                  .Subscribe(observer);

                        return(Disposable.Create(async() =>
                        {
                            // clean up subscription when no observer subscribed
                            lock (topicSubscriptionCache)
                            {
                                messageSubscription.Dispose();
                                topicSubscriptionCache.Remove(topic);
                            }
                            try
                            {
                                await InternalClient.UnsubscribeAsync(topic).ConfigureAwait(false);
                            }
                            catch (ObjectDisposedException) { }         // if disposed there is nothing to unsubscribe
                            catch (Exception exception)
                            {
                                logger.Error(exception, "Error while maintaining unsubscribe from topic.");
                            }
                        }));
                    })
                                 .Publish()   // publish from on source observable
                                 .RefCount(); // count subscriptions and dispose source observable when no subscription

                    // save observable for topic
                    lock (topicSubscriptionCache)
                        topicSubscriptionCache.Add(topic, observable);
                }
                return(observable);
            }
        }
예제 #7
0
        async Task ReceivePackagesLoop(CancellationToken cancellationToken)
        {
            try
            {
                // We do not listen for the cancellation token here because the internal buffer might still
                // contain data to be read even if the TCP connection was already dropped. So we rely on an
                // own exception in the reading loop!
                while (!cancellationToken.IsCancellationRequested)
                {
                    var packet = await ChannelAdapter.ReceivePacketAsync(cancellationToken).ConfigureAwait(false);

                    if (packet == null)
                    {
                        return;
                    }

                    var processPacket = true;

                    if (_eventContainer.InterceptingInboundPacketEvent.HasHandlers)
                    {
                        var interceptingPacketEventArgs = new InterceptingPacketEventArgs
                        {
                            ClientId          = Id,
                            Endpoint          = Endpoint,
                            Packet            = packet,
                            CancellationToken = cancellationToken
                        };

                        await _eventContainer.InterceptingInboundPacketEvent.InvokeAsync(interceptingPacketEventArgs).ConfigureAwait(false);

                        packet        = interceptingPacketEventArgs.Packet;
                        processPacket = interceptingPacketEventArgs.ProcessPacket;
                    }

                    if (!processPacket || packet == null)
                    {
                        // Restart the receiving process to get the next packet ignoring the current one..
                        continue;
                    }

                    Statistics.HandleReceivedPacket(packet);

                    if (packet is MqttPublishPacket publishPacket)
                    {
                        await HandleIncomingPublishPacket(publishPacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttPubAckPacket pubAckPacket)
                    {
                        Session.AcknowledgePublishPacket(pubAckPacket.PacketIdentifier);
                    }
                    else if (packet is MqttPubCompPacket pubCompPacket)
                    {
                        Session.AcknowledgePublishPacket(pubCompPacket.PacketIdentifier);
                    }
                    else if (packet is MqttPubRecPacket pubRecPacket)
                    {
                        HandleIncomingPubRecPacket(pubRecPacket);
                    }
                    else if (packet is MqttPubRelPacket pubRelPacket)
                    {
                        HandleIncomingPubRelPacket(pubRelPacket);
                    }
                    else if (packet is MqttSubscribePacket subscribePacket)
                    {
                        await HandleIncomingSubscribePacket(subscribePacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttUnsubscribePacket unsubscribePacket)
                    {
                        await HandleIncomingUnsubscribePacket(unsubscribePacket, cancellationToken).ConfigureAwait(false);
                    }
                    else if (packet is MqttPingReqPacket)
                    {
                        // See: The Server MUST send a PINGRESP packet in response to a PINGREQ packet [MQTT-3.12.4-1].
                        Session.EnqueueHealthPacket(new MqttPacketBusItem(MqttPingRespPacket.Instance));
                    }
                    else if (packet is MqttPingRespPacket)
                    {
                        throw new MqttProtocolViolationException("A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ Packet only.");
                    }
                    else if (packet is MqttDisconnectPacket)
                    {
                        IsCleanDisconnect = true;
                        return;
                    }
                    else
                    {
                        if (!_packetDispatcher.TryDispatch(packet))
                        {
                            throw new MqttProtocolViolationException($"Received packet '{packet}' at an unexpected time.");
                        }
                    }
                }
            }
            catch (OperationCanceledException)
            {
            }
            catch (Exception exception)
            {
                if (exception is MqttCommunicationException)
                {
                    _logger.Warning(exception, "Client '{0}': Communication exception while receiving client packets.", Id);
                }
                else
                {
                    _logger.Error(exception, "Client '{0}': Error while receiving client packets.", Id);
                }
            }
        }
예제 #8
0
        public async Task <MqttClientConnectResult> ConnectAsync(IMqttClientOptions options, CancellationToken cancellationToken)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.ChannelOptions == null)
            {
                throw new ArgumentException("ChannelOptions are not set.");
            }

            ThrowIfConnected("It is not allowed to connect with a server after the connection is established.");

            ThrowIfDisposed();

            if (CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connecting, MqttClientConnectionStatus.Disconnected) != MqttClientConnectionStatus.Disconnected)
            {
                throw new InvalidOperationException("Not allowed to connect while connect/disconnect is pending.");
            }

            MqttClientConnectResult connectResult = null;

            try
            {
                Options = options;

                _packetIdentifierProvider.Reset();
                _packetDispatcher.CancelAll();

                _backgroundCancellationTokenSource = new CancellationTokenSource();
                var backgroundCancellationToken = _backgroundCancellationTokenSource.Token;

                var adapter = _adapterFactory.CreateClientAdapter(options);
                _adapter = adapter;

                using (var combined = CancellationTokenSource.CreateLinkedTokenSource(backgroundCancellationToken, cancellationToken))
                {
                    _logger.Verbose("Trying to connect with server '{0}' (Timeout={1}).", options.ChannelOptions, options.CommunicationTimeout);
                    await adapter.ConnectAsync(options.CommunicationTimeout, combined.Token).ConfigureAwait(false);

                    _logger.Verbose("Connection with server established.");

                    _publishPacketReceiverQueue = new AsyncQueue <MqttPublishPacket>();
                    _publishPacketReceiverTask  = Task.Run(() => ProcessReceivedPublishPackets(backgroundCancellationToken), backgroundCancellationToken);

                    _packetReceiverTask = Task.Run(() => TryReceivePacketsAsync(backgroundCancellationToken), backgroundCancellationToken);

                    connectResult = await AuthenticateAsync(adapter, options.WillMessage, combined.Token).ConfigureAwait(false);
                }

                _lastPacketSentTimestamp = DateTime.UtcNow;

                if (Options.KeepAlivePeriod != TimeSpan.Zero)
                {
                    _keepAlivePacketsSenderTask = Task.Run(() => TrySendKeepAliveMessagesAsync(backgroundCancellationToken), backgroundCancellationToken);
                }

                CompareExchangeConnectionStatus(MqttClientConnectionStatus.Connected, MqttClientConnectionStatus.Connecting);

                _logger.Info("Connected.");

                var connectedHandler = ConnectedHandler;
                if (connectedHandler != null)
                {
                    await connectedHandler.HandleConnectedAsync(new MqttClientConnectedEventArgs(connectResult)).ConfigureAwait(false);
                }

                return(connectResult);
            }
            catch (Exception exception)
            {
                _disconnectReason = MqttClientDisconnectReason.UnspecifiedError;

                _logger.Error(exception, "Error while connecting with server.");

                await DisconnectInternalAsync(null, exception, connectResult).ConfigureAwait(false);

                throw;
            }
        }