Jim Flynn / Mbed OS aws-iot-device-sdk-mbed-c
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers aws_iot_mqtt_client_yield.c Source File

aws_iot_mqtt_client_yield.c

Go to the documentation of this file.
00001 /*
00002 * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
00003 *
00004 * Licensed under the Apache License, Version 2.0 (the "License").
00005 * You may not use this file except in compliance with the License.
00006 * A copy of the License is located at
00007 *
00008 * http://aws.amazon.com/apache2.0
00009 *
00010 * or in the "license" file accompanying this file. This file is distributed
00011 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
00012 * express or implied. See the License for the specific language governing
00013 * permissions and limitations under the License.
00014 */
00015 
00016 // Based on Eclipse Paho.
00017 /*******************************************************************************
00018  * Copyright (c) 2014 IBM Corp.
00019  *
00020  * All rights reserved. This program and the accompanying materials
00021  * are made available under the terms of the Eclipse Public License v1.0
00022  * and Eclipse Distribution License v1.0 which accompany this distribution.
00023  *
00024  * The Eclipse Public License is available at
00025  *    http://www.eclipse.org/legal/epl-v10.html
00026  * and the Eclipse Distribution License is available at
00027  *   http://www.eclipse.org/org/documents/edl-v10.php.
00028  *
00029  * Contributors:
00030  *    Allan Stockdill-Mander/Ian Craggs - initial API and implementation and/or initial documentation
00031  *******************************************************************************/
00032 
00033 /**
00034  * @file aws_iot_mqtt_client_yield.c
00035  * @brief MQTT client yield API definitions
00036  */
00037 
00038 #ifdef __cplusplus
00039 extern "C" {
00040 #endif
00041 
00042 #include "aws_iot_mqtt_client_common_internal.h"
00043 
00044 /**
00045   * This is for the case when the aws_iot_mqtt_internal_send_packet Fails.
00046   */
00047 static void _aws_iot_mqtt_force_client_disconnect(AWS_IoT_Client *pClient) {
00048     pClient->clientStatus.clientState = CLIENT_STATE_DISCONNECTED_ERROR;
00049     pClient->networkStack.disconnect(&(pClient->networkStack));
00050     pClient->networkStack.destroy(&(pClient->networkStack));
00051 }
00052 
00053 static IoT_Error_t _aws_iot_mqtt_handle_disconnect(AWS_IoT_Client *pClient) {
00054     IoT_Error_t rc;
00055 
00056     FUNC_ENTRY;
00057 //printf("JMF: called aws_iot_mqtt_handle_disconnect\n");
00058     rc = aws_iot_mqtt_disconnect(pClient);
00059     if(rc != AWS_SUCCESS) {
00060         // If the aws_iot_mqtt_internal_send_packet prevents us from sending a disconnect packet then we have to clean the stack
00061         _aws_iot_mqtt_force_client_disconnect(pClient);
00062     }
00063 
00064     if(NULL != pClient->clientData.disconnectHandler) {
00065         pClient->clientData.disconnectHandler(pClient, pClient->clientData.disconnectHandlerData);
00066     }
00067 
00068     /* Reset to 0 since this was not a manual disconnect */
00069     pClient->clientStatus.clientState = CLIENT_STATE_DISCONNECTED_ERROR;
00070 //printf("JMF: %s:%d\n",__FILE__,__LINE__);
00071     FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR);
00072 }
00073 
00074 
00075 static IoT_Error_t _aws_iot_mqtt_handle_reconnect(AWS_IoT_Client *pClient) {
00076     IoT_Error_t rc;
00077 
00078     FUNC_ENTRY;
00079 
00080     if(!has_timer_expired(&(pClient->reconnectDelayTimer))) {
00081         /* awsTimer has not expired. Not time to attempt reconnect yet.
00082          * Return attempting reconnect */
00083         FUNC_EXIT_RC(NETWORK_ATTEMPTING_RECONNECT);
00084     }
00085 
00086     rc = NETWORK_PHYSICAL_LAYER_DISCONNECTED;
00087     if(NULL != pClient->networkStack.isConnected) {
00088         rc = pClient->networkStack.isConnected(&(pClient->networkStack));
00089     }
00090 
00091     if(NETWORK_PHYSICAL_LAYER_CONNECTED == rc) {
00092         rc = aws_iot_mqtt_attempt_reconnect(pClient);
00093         if(NETWORK_RECONNECTED == rc) {
00094             rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_IDLE,
00095                                                CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS);
00096             if(AWS_SUCCESS != rc) {
00097                 FUNC_EXIT_RC(rc);
00098             }
00099             FUNC_EXIT_RC(NETWORK_RECONNECTED);
00100         }
00101     }
00102 
00103     pClient->clientData.currentReconnectWaitInterval *= 2;
00104 
00105     if(AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL < pClient->clientData.currentReconnectWaitInterval) {
00106         FUNC_EXIT_RC(NETWORK_RECONNECT_TIMED_OUT_ERROR);
00107     }
00108     countdown_ms(&(pClient->reconnectDelayTimer), pClient->clientData.currentReconnectWaitInterval);
00109     FUNC_EXIT_RC(rc);
00110 }
00111 
00112 static IoT_Error_t _aws_iot_mqtt_keep_alive(AWS_IoT_Client *pClient) {
00113     IoT_Error_t rc = AWS_SUCCESS;
00114     awsTimer timer;
00115     size_t serialized_len;
00116 
00117     FUNC_ENTRY;
00118 
00119     if(NULL == pClient) {
00120         FUNC_EXIT_RC(NULL_VALUE_ERROR);
00121     }
00122 
00123     if(0 == pClient->clientData.keepAliveInterval) {
00124         FUNC_EXIT_RC(AWS_SUCCESS);
00125     }
00126 
00127     if(!has_timer_expired(&pClient->pingTimer)) {
00128         FUNC_EXIT_RC(AWS_SUCCESS);
00129     }
00130 
00131     if(pClient->clientStatus.isPingOutstanding) {
00132 //printf("JMF1\n");
00133         rc = _aws_iot_mqtt_handle_disconnect(pClient);
00134         FUNC_EXIT_RC(rc);
00135     }
00136 
00137     /* there is no ping outstanding - send one */
00138     init_timer(&timer);
00139 
00140     countdown_ms(&timer, pClient->clientData.commandTimeoutMs);
00141     serialized_len = 0;
00142     rc = aws_iot_mqtt_internal_serialize_zero(pClient->clientData.writeBuf, pClient->clientData.writeBufSize,
00143                                               PINGREQ, &serialized_len);
00144     if(AWS_SUCCESS != rc) {
00145         FUNC_EXIT_RC(rc);
00146     }
00147 
00148     /* send the ping packet */
00149     rc = aws_iot_mqtt_internal_send_packet(pClient, serialized_len, &timer);
00150     if(AWS_SUCCESS != rc) {
00151         //If sending a PING fails we can no longer determine if we are connected.  In this case we decide we are disconnected and begin reconnection attempts
00152 //printf("JMF2\n");
00153         rc = _aws_iot_mqtt_handle_disconnect(pClient);
00154         FUNC_EXIT_RC(rc);
00155     }
00156 
00157     pClient->clientStatus.isPingOutstanding = true;
00158     /* start a timer to wait for PINGRESP from server */
00159     countdown_sec(&pClient->pingTimer, pClient->clientData.keepAliveInterval);
00160 
00161     FUNC_EXIT_RC(AWS_SUCCESS);
00162 }
00163 
00164 /**
00165  * @brief Yield to the MQTT client
00166  *
00167  * Called to yield the current thread to the underlying MQTT client.  This time is used by
00168  * the MQTT client to manage PING requests to monitor the health of the TCP connection as
00169  * well as periodically check the socket receive buffer for subscribe messages.  Yield()
00170  * must be called at a rate faster than the keepalive interval.  It must also be called
00171  * at a rate faster than the incoming message rate as this is the only way the client receives
00172  * processing time to manage incoming messages.
00173  * This is the internal function which is called by the yield API to perform the operation.
00174  * Not meant to be called directly as it doesn't do validations or client state changes
00175  *
00176  * @param pClient Reference to the IoT Client
00177  * @param timeout_ms Maximum number of milliseconds to pass thread execution to the client.
00178  *
00179  * @return An IoT Error Type defining successful/failed client processing.
00180  *         If this call results in an error it is likely the MQTT connection has dropped.
00181  *         iot_is_mqtt_connected can be called to confirm.
00182  */
00183 static IoT_Error_t _aws_iot_mqtt_internal_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms) {
00184     IoT_Error_t yieldRc = AWS_SUCCESS;
00185 
00186     uint8_t packet_type;
00187     ClientState clientState;
00188     awsTimer timer;
00189     init_timer(&timer);
00190     countdown_ms(&timer, timeout_ms);
00191 //printf("JMF: called internal_yeld\n");
00192     FUNC_ENTRY;
00193 
00194     // evaluate timeout at the end of the loop to make sure the actual yield runs at least once
00195     do {
00196         clientState = aws_iot_mqtt_get_client_state(pClient);
00197         if(CLIENT_STATE_PENDING_RECONNECT == clientState) {
00198 //printf("JMF: pending_reconnect\n");
00199             if(AWS_IOT_MQTT_MAX_RECONNECT_WAIT_INTERVAL < pClient->clientData.currentReconnectWaitInterval) {
00200                 yieldRc = NETWORK_RECONNECT_TIMED_OUT_ERROR;
00201                 break;
00202             }
00203 //printf("JMF: do reconnect\n");
00204             yieldRc = _aws_iot_mqtt_handle_reconnect(pClient);
00205             /* Network reconnect attempted, check if yield timer expired before
00206              * doing anything else */
00207             continue;
00208         }
00209 
00210 //printf("JMF: do internal_cycle_read \n");
00211         yieldRc = aws_iot_mqtt_internal_cycle_read(pClient, &timer, &packet_type);
00212         if(AWS_SUCCESS == yieldRc) {
00213             yieldRc = _aws_iot_mqtt_keep_alive(pClient);
00214         } else {
00215             // SSL read and write errors are terminal, connection must be closed and retried
00216             if(NETWORK_SSL_READ_ERROR == yieldRc || NETWORK_SSL_READ_TIMEOUT_ERROR == yieldRc
00217                 || NETWORK_SSL_WRITE_ERROR == yieldRc || NETWORK_SSL_WRITE_TIMEOUT_ERROR == yieldRc) {
00218 //printf("JMF3 %d\n",yieldRc);
00219                 yieldRc = _aws_iot_mqtt_handle_disconnect(pClient);
00220             }
00221         }
00222 
00223 //printf("JMF: keepalive said: %d \n", yieldRc);
00224         if(NETWORK_DISCONNECTED_ERROR == yieldRc) {
00225 //printf("JMF: was a disconnect erro \n");
00226             pClient->clientData.counterNetworkDisconnected++;
00227             if(1 == pClient->clientStatus.isAutoReconnectEnabled) {
00228                 yieldRc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_DISCONNECTED_ERROR,
00229                                                         CLIENT_STATE_PENDING_RECONNECT);
00230                 if(AWS_SUCCESS != yieldRc) {
00231                     FUNC_EXIT_RC(yieldRc);
00232                 }
00233 
00234                 pClient->clientData.currentReconnectWaitInterval = AWS_IOT_MQTT_MIN_RECONNECT_WAIT_INTERVAL;
00235                 countdown_ms(&(pClient->reconnectDelayTimer), pClient->clientData.currentReconnectWaitInterval);
00236                 /* Depending on timer values, it is possible that yield timer has expired
00237                  * Set to rc to attempting reconnect to inform client that autoreconnect
00238                  * attempt has started */
00239                 yieldRc = NETWORK_ATTEMPTING_RECONNECT;
00240             } else {
00241                 break;
00242             }
00243         } else if(AWS_SUCCESS != yieldRc) {
00244             break;
00245         }
00246     } while(!has_timer_expired(&timer));
00247 //printf("JMF: exit internal_yield with %d\n",yieldRc);
00248 
00249     FUNC_EXIT_RC(yieldRc);
00250 }
00251 
00252 /**
00253  * @brief Yield to the MQTT client
00254  *
00255  * Called to yield the current thread to the underlying MQTT client.  This time is used by
00256  * the MQTT client to manage PING requests to monitor the health of the TCP connection as
00257  * well as periodically check the socket receive buffer for subscribe messages.  Yield()
00258  * must be called at a rate faster than the keepalive interval.  It must also be called
00259  * at a rate faster than the incoming message rate as this is the only way the client receives
00260  * processing time to manage incoming messages.
00261  * This is the outer function which does the validations and calls the internal yield above
00262  * to perform the actual operation. It is also responsible for client state changes
00263  *
00264  * @param pClient Reference to the IoT Client
00265  * @param timeout_ms Maximum number of milliseconds to pass thread execution to the client.
00266  *
00267  * @return An IoT Error Type defining successful/failed client processing.
00268  *         If this call results in an error it is likely the MQTT connection has dropped.
00269  *         iot_is_mqtt_connected can be called to confirm.
00270  */
00271 IoT_Error_t aws_iot_mqtt_yield(AWS_IoT_Client *pClient, uint32_t timeout_ms) {
00272     IoT_Error_t rc, yieldRc;
00273     ClientState clientState;
00274 
00275     if(NULL == pClient || 0 == timeout_ms) {
00276         FUNC_EXIT_RC(NULL_VALUE_ERROR);
00277     }
00278 
00279     clientState = aws_iot_mqtt_get_client_state(pClient);
00280     /* Check if network was manually disconnected */
00281     if(CLIENT_STATE_DISCONNECTED_MANUALLY == clientState) {
00282         FUNC_EXIT_RC(NETWORK_MANUALLY_DISCONNECTED);
00283     }
00284 
00285     /* If we are in the pending reconnect state, skip other checks.
00286      * Pending reconnect state is only set when auto-reconnect is enabled */
00287     if(CLIENT_STATE_PENDING_RECONNECT != clientState) {
00288         /* Check if network is disconnected and auto-reconnect is not enabled */
00289         if(!aws_iot_mqtt_is_client_connected(pClient)) {
00290 //printf("JMF: %s:%d\n",__FILE__,__LINE__);
00291             FUNC_EXIT_RC(NETWORK_DISCONNECTED_ERROR);
00292         }
00293 
00294         /* Check if client is idle, if not another operation is in progress and we should return */
00295         if(CLIENT_STATE_CONNECTED_IDLE != clientState) {
00296             FUNC_EXIT_RC(MQTT_CLIENT_NOT_IDLE_ERROR);
00297         }
00298 
00299         rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_IDLE,
00300                                            CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS);
00301         if(AWS_SUCCESS != rc) {
00302             FUNC_EXIT_RC(rc);
00303         }
00304     }
00305 
00306     yieldRc = _aws_iot_mqtt_internal_yield(pClient, timeout_ms);
00307 
00308     if(NETWORK_DISCONNECTED_ERROR != yieldRc && NETWORK_ATTEMPTING_RECONNECT != yieldRc) {
00309         rc = aws_iot_mqtt_set_client_state(pClient, CLIENT_STATE_CONNECTED_YIELD_IN_PROGRESS,
00310                                            CLIENT_STATE_CONNECTED_IDLE);
00311         if(AWS_SUCCESS == yieldRc && AWS_SUCCESS != rc) {
00312             yieldRc = rc;
00313         }
00314     }
00315 
00316     FUNC_EXIT_RC(yieldRc);
00317 }
00318 
00319 #ifdef __cplusplus
00320 }
00321 #endif
00322