private static MqttMsgBase HandleWaitForPubrec(
            MqttClientConnection clientConnection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            ref bool msgReceivedProcessed,
            ref int timeout)
        {
            // QoS 2, waiting for PUBREC of a PUBLISH message sent
            if (msgContext.Flow != MqttMsgFlow.ToPublish)
            {
                return(null);
            }

            MqttMsgBase msgReceived;

            if (!clientConnection.InternalQueue.TryPeek(out msgReceived))
            {
                return(null);
            }

            bool          acknowledge = false;
            InternalEvent internalEvent;

            // it is a PUBREC message
            if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE)
            {
                // PUBREC message for the current PUBLISH message, send PUBREL, wait for PUBCOMP
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    // received message processed
                    MqttMsgBase dequeuedMsg;
                    clientConnection.InternalQueue.TryDequeue(out dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    MqttOutgoingMessageManager.Pubrel(clientConnection, msgInflight.MessageId, false);

                    msgContext.State     = MqttMsgState.WaitForPubcomp;
                    msgContext.Timestamp = Environment.TickCount;
                    msgContext.Attempt   = 1;

                    // update timeout : minimum between delay (based on current message sent) or current timeout
                    timeout = (clientConnection.Settings.DelayOnRetry < timeout)
                                  ? clientConnection.Settings.DelayOnRetry
                                  : timeout;

                    // re-enqueue message
                    clientConnection.EnqueueInflight(msgContext);
                }
            }

            // current message not acknowledged
            if (!acknowledge)
            {
                var delta = Environment.TickCount - msgContext.Timestamp;
                // check timeout for receiving PUBREC since PUBLISH was sent
                if (delta >= clientConnection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < clientConnection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.QueuedQos2;

                        // re-enqueue message
                        clientConnection.EnqueueInflight(msgContext);

                        // update timeout (0 -> reanalyze queue immediately)
                        timeout = 0;
                    }
                    else
                    {
                        // PUBREC not received in time, PUBLISH retries failed, need to remove from session inflight messages too
                        if ((clientConnection.Session != null) &&
                            (clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key)))
                        {
                            MqttMsgContext contextToBeRemoved;
                            clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved);
                        }

                        // if PUBREC for a PUBLISH message not received after retries, raise event for not published
                        internalEvent = new MsgPublishedInternalEvent(msgInflight, false);
                        // notify not received acknowledge from broker and message not published
                        clientConnection.EnqueueInternalEvent(internalEvent);
                    }
                }
                else
                {
                    // re-enqueue message
                    clientConnection.EnqueueInflight(msgContext);

                    // update timeout
                    int msgTimeout = (clientConnection.Settings.DelayOnRetry - delta);
                    timeout = (msgTimeout < timeout) ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }
        private static MqttMsgBase HandleWaitForPubackSubackUbsuback(
            MqttClientConnection clientConnection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            ref bool msgReceivedProcessed,
            ref int timeout)
        {
            // QoS 1, waiting for PUBACK of a PUBLISH message sent or
            //        waiting for SUBACK of a SUBSCRIBE message sent or
            //        waiting for UNSUBACK of a UNSUBSCRIBE message sent or
            if (msgContext.Flow != MqttMsgFlow.ToPublish)
            {
                return(null);
            }

            MqttMsgBase msgReceived;

            if (!clientConnection.InternalQueue.TryPeek(out msgReceived))
            {
                return(null);
            }

            bool          acknowledge = false;
            InternalEvent internalEvent;

            // PUBACK message or SUBACK/UNSUBACK message for the current message
            if (((msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBACK_TYPE) &&
                 (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) &&
                 (msgReceived.MessageId == msgInflight.MessageId)) ||
                ((msgReceived.Type == MqttMsgBase.MQTT_MSG_SUBACK_TYPE) &&
                 (msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE) &&
                 (msgReceived.MessageId == msgInflight.MessageId)) ||
                ((msgReceived.Type == MqttMsgBase.MQTT_MSG_UNSUBACK_TYPE) &&
                 (msgInflight.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE) &&
                 (msgReceived.MessageId == msgInflight.MessageId)))
            {
                // received message processed
                MqttMsgBase dequeuedMsg;
                clientConnection.InternalQueue.TryDequeue(out dequeuedMsg);
                acknowledge          = true;
                msgReceivedProcessed = true;

                // if PUBACK received, confirm published with flag
                if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBACK_TYPE)
                {
                    internalEvent = new MsgPublishedInternalEvent(msgReceived, true);
                }
                else
                {
                    internalEvent = new MsgInternalEvent(msgReceived);
                }

                // notify received acknowledge from broker of a published message or subscribe/unsubscribe message
                clientConnection.EnqueueInternalEvent(internalEvent);

                // PUBACK received for PUBLISH message with QoS Level 1, remove from session state
                if ((msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) && (clientConnection.Session != null) &&
                    (clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key)))
                {
                    MqttMsgContext contextToBeRemoved;
                    clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved);
                }
            }

            // current message not acknowledged, no PUBACK or SUBACK/UNSUBACK or not equal messageid
            if (!acknowledge)
            {
                var delta = Environment.TickCount - msgContext.Timestamp;
                // check timeout for receiving PUBACK since PUBLISH was sent or
                // for receiving SUBACK since SUBSCRIBE was sent or
                // for receiving UNSUBACK since UNSUBSCRIBE was sent
                if (delta >= clientConnection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < clientConnection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.QueuedQos1;

                        // re-enqueue message
                        clientConnection.EnqueueInflight(msgContext);

                        // update timeout (0 -> reanalyze queue immediately)
                        timeout = 0;
                    }
                    else
                    {
                        // if PUBACK for a PUBLISH message not received after retries, raise event for not published
                        if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE)
                        {
                            // PUBACK not received in time, PUBLISH retries failed, need to remove from session inflight messages too
                            if ((clientConnection.Session != null) &&
                                (clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key)))
                            {
                                MqttMsgContext contextToBeRemoved;
                                clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved);
                            }

                            internalEvent = new MsgPublishedInternalEvent(msgInflight, false);

                            // notify not received acknowledge from broker and message not published
                            clientConnection.EnqueueInternalEvent(internalEvent);
                        }
                        // NOTE : not raise events for SUBACK or UNSUBACK not received
                        //        for the user no event raised means subscribe/unsubscribe failed
                    }
                }
                else
                {
                    // re-enqueue message (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK)
                    clientConnection.EnqueueInflight(msgContext);

                    // update timeout
                    int msgTimeout = (clientConnection.Settings.DelayOnRetry - delta);
                    timeout = (msgTimeout < timeout) ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }
        private static MqttMsgBase WaitForPubcomp(
            MqttClientConnection clientConnection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            ref bool msgReceivedProcessed,
            ref int timeout)
        {
            // QoS 2, waiting for PUBCOMP of a PUBREL message sent
            if (msgContext.Flow != MqttMsgFlow.ToPublish)
            {
                return(null);
            }

            MqttMsgBase msgReceived;

            if (!clientConnection.InternalQueue.TryPeek(out msgReceived))
            {
                return(null);
            }

            bool          acknowledge = false;
            InternalEvent internalEvent;

            // it is a PUBCOMP message
            if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBCOMP_TYPE)
            {
                // PUBCOMP message for the current message
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    MqttMsgBase dequeuedMsg;
                    clientConnection.InternalQueue.TryDequeue(out dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    internalEvent = new MsgPublishedInternalEvent(msgReceived, true);
                    // notify received acknowledge from broker of a published message
                    clientConnection.EnqueueInternalEvent(internalEvent);

                    // PUBCOMP received for PUBLISH message with QoS Level 2, remove from session state
                    if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE &&
                        clientConnection.Session != null &&
                        clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key))
                    {
                        MqttMsgContext contextToBeRemoved;
                        clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved);
                    }
                }
            }
            // it is a PUBREC message
            else if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE)
            {
                // another PUBREC message for the current message due to a retransmitted PUBLISH
                // I'm in waiting for PUBCOMP, so I can discard clientConnection PUBREC
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    MqttMsgBase dequeuedMsg;
                    clientConnection.InternalQueue.TryDequeue(out dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    // re-enqueue message
                    clientConnection.EnqueueInflight(msgContext);
                }
            }

            // current message not acknowledged
            if (!acknowledge)
            {
                var delta = Environment.TickCount - msgContext.Timestamp;
                // check timeout for receiving PUBCOMP since PUBREL was sent
                if (delta >= clientConnection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < clientConnection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.SendPubrel;

                        // re-enqueue message
                        clientConnection.EnqueueInflight(msgContext);

                        // update timeout (0 -> reanalyze queue immediately)
                        timeout = 0;
                    }
                    else
                    {
                        // PUBCOMP not received, PUBREL retries failed, need to remove from session inflight messages too
                        if ((clientConnection.Session != null) && clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key))
                        {
                            MqttMsgContext contextToBeRemoved;
                            clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved);
                        }

                        // if PUBCOMP for a PUBLISH message not received after retries, raise event for not published
                        internalEvent = new MsgPublishedInternalEvent(msgInflight, false);
                        // notify not received acknowledge from broker and message not published
                        clientConnection.EnqueueInternalEvent(internalEvent);
                    }
                }
                else
                {
                    // re-enqueue message
                    clientConnection.EnqueueInflight(msgContext);

                    // update timeout
                    int msgTimeout = clientConnection.Settings.DelayOnRetry - delta;
                    timeout = (msgTimeout < timeout) ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }