private MqttMsgBase WaitForPubrel(
            MqttConnection connection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            ref bool msgReceivedProcessed)
        {
            // QoS 2, waiting for PUBREL of a PUBREC message sent
            if (msgContext.Flow != MqttMsgFlow.ToAcknowledge)
            {
                return(null);
            }

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

            InternalEvent internalEvent;

            // it is a PUBREL message
            if (msgReceived != null && msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREL_TYPE)
            {
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    // received message processed
                    connection.InternalQueue.TryDequeue(out MqttMsgBase dequeuedMsg);
                    msgReceivedProcessed = true;

                    outgoingMessageHandler.Pubcomp(connection, msgInflight.MessageId);

                    internalEvent = new InternalEvent(msgInflight);

                    // notify published message from broker and acknowledged
                    connection.EnqueueInternalEvent(internalEvent);

                    // PUBREL received (and PUBCOMP sent) for PUBLISH message with QoS Level 2, remove from session state
                    if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE && connection.Session != null &&
                        connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                    {
                        connection.Session.InflightMessages.TryRemove(
                            msgContext.Key,
                            out MqttMsgContext contextToBeRemoved);
                    }
                }
                else
                {
                    // re-enqueue message
                    connection.EnqueueInflight(msgContext);
                }
            }
            else
            {
                connection.EnqueueInflight(msgContext);
            }

            return(msgReceived);
        }
        private int SendPubrel(
            MqttConnection connection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            int timeout)
        {
            // QoS 2, PUBREL message to send to broker, state change to wait PUBCOMP
            if (msgContext.Flow == MqttMsgFlow.ToPublish)
            {
                msgContext.State     = MqttMsgState.WaitForPubcomp;
                msgContext.Timestamp = Environment.TickCount;
                msgContext.Attempt++;

                // retry ? set dup flag [v3.1.1] no needed
                if (connection.ProtocolVersion == MqttProtocolVersion.Version_3_1 && msgContext.Attempt > 1)
                {
                    outgoingMessageHandler.Pubrel(connection, msgInflight.MessageId, true);
                }
                else
                {
                    outgoingMessageHandler.Pubrel(connection, msgInflight.MessageId, false);
                }

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

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

            return(timeout);
        }
        private int HandleQueuedQos1SendSubscribeAndSendUnsubscribe(
            MqttConnection connection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            int timeout)
        {
            InternalEvent internalEvent;

            // QoS 1, PUBLISH or SUBSCRIBE/UNSUBSCRIBE message to send to broker, state change to wait PUBACK or SUBACK/UNSUBACK
            if (msgContext.Flow == MqttMsgFlow.ToPublish)
            {
                msgContext.Timestamp = Environment.TickCount;
                msgContext.Attempt++;

                if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE)
                {
                    // PUBLISH message to send, wait for PUBACK
                    msgContext.State = MqttMsgState.WaitForPuback;

                    // retry ? set dup flag [v3.1.1] only for PUBLISH message
                    if (msgContext.Attempt > 1)
                    {
                        msgInflight.DupFlag = true;
                    }
                }
                else if (msgInflight.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE)
                {
                    // SUBSCRIBE message to send, wait for SUBACK
                    msgContext.State = MqttMsgState.WaitForSuback;
                }
                else if (msgInflight.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE)
                {
                    // UNSUBSCRIBE message to send, wait for UNSUBACK
                    msgContext.State = MqttMsgState.WaitForUnsuback;
                }

                outgoingMessageHandler.Send(connection, msgInflight);

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

                // re-enqueue message (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK)
                connection.EnqueueInflight(msgContext);
            }

            // QoS 1, PUBLISH message received from broker to acknowledge, send PUBACK
            else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge)
            {
                outgoingMessageHandler.Puback(connection, msgInflight.MessageId);

                internalEvent = new InternalEvent(msgInflight);

                // notify published message from broker and acknowledged
                connection.EnqueueInternalEvent(internalEvent);
            }

            return(timeout);
        }
        private int HandleQueuedQos2(
            MqttConnection connection,
            MqttMsgContext msgContext,
            MqttMsgBase msgInflight,
            int timeout)
        {
            // QoS 2, PUBLISH message to send to broker, state change to wait PUBREC
            if (msgContext.Flow == MqttMsgFlow.ToPublish)
            {
                msgContext.Timestamp = Environment.TickCount;
                msgContext.Attempt++;
                msgContext.State = MqttMsgState.WaitForPubrec;

                // retry ? set dup flag
                if (msgContext.Attempt > 1)
                {
                    msgInflight.DupFlag = true;
                }

                outgoingMessageHandler.Send(connection, msgInflight);

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

                // re-enqueue message (I have to re-analyze for receiving PUBREC)
                connection.EnqueueInflight(msgContext);
            }

            // QoS 2, PUBLISH message received from broker to acknowledge, send PUBREC, state change to wait PUBREL
            else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge)
            {
                msgContext.State = MqttMsgState.WaitForPubrel;

                outgoingMessageHandler.Pubrec(connection, msgInflight.MessageId);

                // re-enqueue message (I have to re-analyze for receiving PUBREL)
                connection.EnqueueInflight(msgContext);
            }

            return(timeout);
        }
        private void EnqueueInflight(MqttConnection connection, MqttMsgBase msg, MqttMsgFlow flow)
        {
            // enqueue is needed (or not)
            var enqueue = true;

            // if it is a PUBLISH message with QoS Level 2
            if (msg.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE && msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE)
            {
                // if it is a PUBLISH message already received (it is in the inflight queue), the publisher
                // re-sent it because it didn't received the PUBREC. In connection case, we have to re-send PUBREC

                // NOTE : I need to find on message id and flow because the broker could be publish/received
                // to/from client and message id could be the same (one tracked by broker and the other by client)
                var msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToAcknowledge);
                var msgCtx       = (MqttMsgContext)connection.InflightQueue.FirstOrDefault(msgCtxFinder.Find);

                // the PUBLISH message is alredy in the inflight queue, we don't need to re-enqueue but we need
                // to change state to re-send PUBREC
                if (msgCtx != null)
                {
                    msgCtx.State = MqttMsgState.QueuedQos2;
                    msgCtx.Flow  = MqttMsgFlow.ToAcknowledge;
                    enqueue      = false;
                }
            }

            if (enqueue)
            {
                // set a default state
                var state = MqttMsgState.QueuedQos0;

                // based on QoS level, the messages flow between broker and client changes
                switch (msg.QosLevel)
                {
                // QoS Level 0
                case MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE:
                    state = MqttMsgState.QueuedQos0;
                    break;

                // QoS Level 1
                case MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE:
                    state = MqttMsgState.QueuedQos1;
                    break;

                // QoS Level 2
                case MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE:
                    state = MqttMsgState.QueuedQos2;
                    break;
                }

                // [v3.1.1] SUBSCRIBE and UNSUBSCRIBE aren't "officially" QOS = 1
                // so QueuedQos1 state isn't valid for them
                if (msg.Type == MqttMsgBase.MQTT_MSG_SUBSCRIBE_TYPE)
                {
                    state = MqttMsgState.SendSubscribe;
                }
                else if (msg.Type == MqttMsgBase.MQTT_MSG_UNSUBSCRIBE_TYPE)
                {
                    state = MqttMsgState.SendUnsubscribe;
                }

                // queue message context
                var msgContext = new MqttMsgContext()
                {
                    Message = msg,
                    State   = state,
                    Flow    = flow,
                    Attempt = 0
                };

                // enqueue message and unlock send thread
                connection.EnqueueInflight(msgContext);

                // PUBLISH message
                if (msg.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE)
                {
                    if (msgContext.Flow == MqttMsgFlow.ToPublish &&
                        (msg.QosLevel == MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE ||
                         msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE))
                    {
                        if (connection.Session != null)
                        {
                            connection.Session.InflightMessages.TryAdd(msgContext.Key, msgContext);
                        }
                    }

                    // to acknowledge and QoS level 2
                    else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge &&
                             msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE)
                    {
                        if (connection.Session != null)
                        {
                            connection.Session.InflightMessages.TryAdd(msgContext.Key, msgContext);
                        }
                    }
                }
            }
        }
        private MqttMsgBase WaitForPubcomp(
            MqttConnection connection,
            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);
            }

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

            var           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)
                {
                    connection.InternalQueue.TryDequeue(out MqttMsgBase dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    internalEvent = new PublishedInternalEvent(msgReceived, true);

                    // notify received acknowledge from broker of a published message
                    connection.EnqueueInternalEvent(internalEvent);

                    // PUBCOMP received for PUBLISH message with QoS Level 2, remove from session state
                    if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE && connection.Session != null &&
                        connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                    {
                        connection.Session.InflightMessages.TryRemove(
                            msgContext.Key,
                            out MqttMsgContext 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 connection PUBREC
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    connection.InternalQueue.TryDequeue(out MqttMsgBase dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    // re-enqueue message
                    connection.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 >= connection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < connection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.SendPubrel;

                        // re-enqueue message
                        connection.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 (connection.Session != null &&
                            connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                        {
                            connection.Session.InflightMessages.TryRemove(
                                msgContext.Key,
                                out MqttMsgContext contextToBeRemoved);
                        }

                        // if PUBCOMP for a PUBLISH message not received after retries, raise event for not published
                        internalEvent = new PublishedInternalEvent(msgInflight, false);

                        // notify not received acknowledge from broker and message not published
                        connection.EnqueueInternalEvent(internalEvent);
                    }
                }
                else
                {
                    // re-enqueue message
                    connection.EnqueueInflight(msgContext);

                    // update timeout
                    var msgTimeout = connection.Settings.DelayOnRetry - delta;
                    timeout = msgTimeout < timeout ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }
        private MqttMsgBase HandleWaitForPubrec(
            MqttConnection connection,
            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);
            }

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

            var           acknowledge = false;
            InternalEvent internalEvent;

            // it is a PUBREC message
            if (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREC_TYPE)
            {
                if (msgReceived.MessageId == msgInflight.MessageId)
                {
                    // received message processed
                    connection.InternalQueue.TryDequeue(out MqttMsgBase dequeuedMsg);
                    acknowledge          = true;
                    msgReceivedProcessed = true;

                    outgoingMessageHandler.Pubrel(connection, 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 = connection.Settings.DelayOnRetry < timeout ? connection.Settings.DelayOnRetry : timeout;

                    // re-enqueue message
                    connection.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 >= connection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < connection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.QueuedQos2;

                        // re-enqueue message
                        connection.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 (connection.Session != null &&
                            connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                        {
                            connection.Session.InflightMessages.TryRemove(
                                msgContext.Key,
                                out MqttMsgContext contextToBeRemoved);
                        }

                        // if PUBREC for a PUBLISH message not received after retries, raise event for not published
                        internalEvent = new PublishedInternalEvent(msgInflight, false);

                        // notify not received acknowledge from broker and message not published
                        connection.EnqueueInternalEvent(internalEvent);
                    }
                }
                else
                {
                    // re-enqueue message
                    connection.EnqueueInflight(msgContext);

                    // update timeout
                    var msgTimeout = connection.Settings.DelayOnRetry - delta;
                    timeout = msgTimeout < timeout ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }
        private MqttMsgBase HandleWaitForPubackSubackUbsuback(
            MqttConnection connection,
            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);
            }

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

            var           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
                connection.InternalQueue.TryDequeue(out MqttMsgBase dequeuedMsg);
                acknowledge          = true;
                msgReceivedProcessed = true;

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

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

                // PUBACK received for PUBLISH message with QoS Level 1, remove from session state
                if (msgInflight.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE && connection.Session != null &&
                    connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                {
                    connection.Session.InflightMessages.TryRemove(msgContext.Key, out MqttMsgContext 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 >= connection.Settings.DelayOnRetry)
                {
                    // max retry not reached, resend
                    if (msgContext.Attempt < connection.Settings.AttemptsOnRetry)
                    {
                        msgContext.State = MqttMsgState.QueuedQos1;

                        // re-enqueue message
                        connection.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 (connection.Session != null &&
                                connection.Session.InflightMessages.ContainsKey(msgContext.Key))
                            {
                                connection.Session.InflightMessages.TryRemove(
                                    msgContext.Key,
                                    out MqttMsgContext contextToBeRemoved);
                            }

                            internalEvent = new PublishedInternalEvent(msgInflight, false);

                            // notify not received acknowledge from broker and message not published
                            connection.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)
                    connection.EnqueueInflight(msgContext);

                    // update timeout
                    var msgTimeout = connection.Settings.DelayOnRetry - delta;
                    timeout = msgTimeout < timeout ? msgTimeout : timeout;
                }
            }

            return(msgReceived);
        }