private static MqttMsgBase WaitForPubrel(MqttClientConnection clientConnection, MqttMsgContext msgContext, MqttMsgBase msgInflight, ref bool msgReceivedProcessed) { // QoS 2, waiting for PUBREL of a PUBREC message sent if (msgContext.Flow != MqttMsgFlow.ToAcknowledge) { return(null); } MqttMsgBase msgReceived; if (!clientConnection.InternalQueue.TryPeek(out msgReceived)) { return(null); } InternalEvent internalEvent; // it is a PUBREL message if ((msgReceived != null) && (msgReceived.Type == MqttMsgBase.MQTT_MSG_PUBREL_TYPE)) { // PUBREL message for the current message, send PUBCOMP if (msgReceived.MessageId == msgInflight.MessageId) { // received message processed MqttMsgBase dequeuedMsg; clientConnection.InternalQueue.TryDequeue(out dequeuedMsg); msgReceivedProcessed = true; MqttOutgoingMessageManager.Pubcomp(clientConnection, msgInflight.MessageId); internalEvent = new MsgInternalEvent(msgInflight); // notify published message from broker and acknowledged clientConnection.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) && (clientConnection.Session != null) && (clientConnection.Session.InflightMessages.ContainsKey(msgContext.Key))) { MqttMsgContext contextToBeRemoved; clientConnection.Session.InflightMessages.TryRemove(msgContext.Key, out contextToBeRemoved); } } else { // re-enqueue message clientConnection.EnqueueInflight(msgContext); } } else { // re-enqueue message clientConnection.EnqueueInflight(msgContext); } return(msgReceived); }
private static int SendPubrel(MqttClientConnection clientConnection, 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 (clientConnection.ProtocolVersion == MqttProtocolVersion.Version_3_1 && msgContext.Attempt > 1) { MqttOutgoingMessageManager.Pubrel(clientConnection, msgInflight.MessageId, true); } else { MqttOutgoingMessageManager.Pubrel(clientConnection, msgInflight.MessageId, false); } // 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); } return(timeout); }
private static int HandleQueuedQos1SendSubscribeAndSendUnsubscribe( MqttClientConnection clientConnection, 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; } MqttOutgoingMessageManager.Send(clientConnection, msgInflight); // 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 (I have to re-analyze for receiving PUBACK, SUBACK or UNSUBACK) clientConnection.EnqueueInflight(msgContext); } // QoS 1, PUBLISH message received from broker to acknowledge, send PUBACK else if (msgContext.Flow == MqttMsgFlow.ToAcknowledge) { MqttOutgoingMessageManager.Puback(clientConnection, msgInflight.MessageId); internalEvent = new MsgInternalEvent(msgInflight); // notify published message from broker and acknowledged clientConnection.EnqueueInternalEvent(internalEvent); } return(timeout); }
private static int HandleQueuedQos2( MqttClientConnection clientConnection, 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; } MqttOutgoingMessageManager.Send(clientConnection, msgInflight); // 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 (I have to re-analyze for receiving PUBREC) clientConnection.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; MqttOutgoingMessageManager.Pubrec(clientConnection, msgInflight.MessageId); // re-enqueue message (I have to re-analyze for receiving PUBREL) clientConnection.EnqueueInflight(msgContext); } return(timeout); }
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 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 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); }
private static void EnqueueInflight(MqttClientConnection clientConnection, MqttMsgBase msg, MqttMsgFlow flow) { // enqueue is needed (or not) bool 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 clientConnection 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) MqttMsgContextFinder msgCtxFinder = new MqttMsgContextFinder(msg.MessageId, MqttMsgFlow.ToAcknowledge); MqttMsgContext msgCtx = (MqttMsgContext)clientConnection.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 MqttMsgState 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 MqttMsgContext msgContext = new MqttMsgContext() { Message = msg, State = state, Flow = flow, Attempt = 0 }; // enqueue message and unlock send thread clientConnection.EnqueueInflight(msgContext); // PUBLISH message if (msg.Type == MqttMsgBase.MQTT_MSG_PUBLISH_TYPE) { // to publish and QoS level 1 or 2 if ((msgContext.Flow == MqttMsgFlow.ToPublish) && ((msg.QosLevel == MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE) || (msg.QosLevel == MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE))) { if (clientConnection.Session != null) { clientConnection.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 (clientConnection.Session != null) { clientConnection.Session.InflightMessages.TryAdd(msgContext.Key, msgContext); } } } } }