diff --git a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/DemoTasks/PlaintextMQTTExample.c b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/DemoTasks/PlaintextMQTTExample.c index da314ac537..e5ac5379a9 100644 --- a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/DemoTasks/PlaintextMQTTExample.c +++ b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/DemoTasks/PlaintextMQTTExample.c @@ -57,6 +57,9 @@ /* MQTT library includes. */ #include "mqtt.h" +/* Retry utilities include. */ +#include "retry_utils.h" + /* Transport interface include. */ #include "plaintext_freertos.h" @@ -163,23 +166,46 @@ */ static void prvMQTTDemoTask( void * pvParameters ); +/** + * @brief Connect to MQTT broker with reconnection retries. + * + * If connection fails, retry is attempted after a timeout. + * Timeout value will exponentially increase until maximum + * timeout value is reached or the number of attempts are exhausted. + * + * @param pxNetworkContext The output parameter to return the created network context. + * + * @return The status of the final connection attempt. + */ +static PlaintextTransportStatus_t prvConnectToServerWithBackoffRetries( NetworkContext_t * pxNetworkContext ); + /** * @brief Sends an MQTT Connect packet over the already connected TCP socket. * * @param pxMQTTContext MQTT context pointer. - * @param xNetworkContext network context. + * @param pxNetworkContext Network context. * */ static void prvCreateMQTTConnectionWithBroker( MQTTContext_t * pxMQTTContext, NetworkContext_t * pxNetworkContext ); +/** + * @brief Function to update variable globalSubAckStatus with status + * information from Subscribe ACK. Called by eventCallback after processing + * incoming subscribe echo. + * + * @param Server response to the subscription request. + */ +static void prvUpdateSubAckStatus( MQTTPacketInfo_t * pxPacketInfo ); + /** * @brief Subscribes to the topic as specified in mqttexampleTOPIC at the top of - * this file. + * this file. In the case of a Subscribe ACK failure, then subscription is + * retried using an exponential backoff strategy with jitter. * * @param pxMQTTContext MQTT context pointer. */ -static void prvMQTTSubscribeToTopic( MQTTContext_t * pxMQTTContext ); +static void prvMQTTSubscribeWithBackoffRetries( MQTTContext_t * pxMQTTContext ); /** * @brief Publishes a message mqttexampleMESSAGE on mqttexampleTOPIC topic. @@ -260,6 +286,18 @@ static uint16_t usSubscribePacketIdentifier; */ static uint16_t usUnsubscribePacketIdentifier; +/** + * @brief Status of latest Subscribe ACK; + * it is updated every time the callback function processes a Subscribe ACK. + */ +static MQTTSubAckStatus_t xGlobalSubAckStatus = MQTTSubAckFailure; + +/** + * @brief Array to keep subscription topics. + * Used to re-subscribe to topics that failed initial subscription attempts. + */ +static MQTTSubscribeInfo_t xGlobalSubscribeInfo; + /** @brief Static buffer used to hold MQTT messages being sent and received. */ static MQTTFixedBuffer_t xBuffer = @@ -270,7 +308,7 @@ static MQTTFixedBuffer_t xBuffer = /*-----------------------------------------------------------*/ -/* +/** * @brief Create the task that demonstrates the Plain text MQTT API Demo. */ void vStartSimpleMQTTDemo( void ) @@ -305,15 +343,12 @@ static void prvMQTTDemoTask( void * pvParameters ) { /****************************** Connect. ******************************/ - /* Establish a TCP connection with the MQTT broker. This example connects to - * the MQTT broker as specified in democonfigMQTT_BROKER_ENDPOINT and - * democonfigMQTT_BROKER_PORT at the top of this file. */ - LogInfo( ( "Create a TCP connection to %s.\r\n", democonfigMQTT_BROKER_ENDPOINT ) ); - xNetworkStatus = Plaintext_FreeRTOS_Connect( &xNetworkContext, - democonfigMQTT_BROKER_ENDPOINT, - democonfigMQTT_BROKER_PORT, - TRANSPORT_SEND_RECV_TIMEOUT_MS, - TRANSPORT_SEND_RECV_TIMEOUT_MS ); + /* Attempt to connect to the MQTT broker. If connection fails, retry after + * a timeout. Timeout value will be exponentially increased until the maximum + * number of attempts are reached or the maximum timeout value is reached. + * The function returns a failure status if the TCP connection cannot be established + * to the broker after the configured number of attempts. */ + xNetworkStatus = prvConnectToServerWithBackoffRetries( &xNetworkContext ); configASSERT( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ); /* Sends an MQTT Connect packet over the already connected TCP socket, @@ -323,25 +358,10 @@ static void prvMQTTDemoTask( void * pvParameters ) /**************************** Subscribe. ******************************/ - /* The client is now connected to the broker. Subscribe to the topic - * as specified in mqttexampleTOPIC at the top of this file by sending a - * subscribe packet then waiting for a subscribe acknowledgment (SUBACK). - * This client will then publish to the same topic it subscribed to, so it - * will expect all the messages it sends to the broker to be sent back to it - * from the broker. This demo uses QOS0 in Subscribe, therefore, the Publish - * messages received from the broker will have QOS0. */ - LogInfo( ( "Attempt to subscribe to the MQTT topic %s.\r\n", mqttexampleTOPIC ) ); - prvMQTTSubscribeToTopic( &xMQTTContext ); - - /* Process incoming packet from the broker. After sending the subscribe, the - * client may receive a publish before it receives a subscribe ack. Therefore, - * call generic incoming packet processing function. Since this demo is - * subscribing to the topic to which no one is publishing, probability of - * receiving Publish message before subscribe ack is zero; but application - * must be ready to receive any packet. This demo uses the generic packet - * processing function everywhere to highlight this fact. */ - xMQTTStatus = MQTT_ProcessLoop( &xMQTTContext, mqttexamplePROCESS_LOOP_TIMEOUT_MS ); - configASSERT( xMQTTStatus == MQTTSuccess ); + /* If server rejected the subscription request, attempt to resubscribe to topic. + * Attempts are made according to the exponential backoff retry strategy + * implemented in retryUtils. */ + prvMQTTSubscribeWithBackoffRetries( &xMQTTContext ); /**************************** Publish and Keep Alive Loop. ******************************/ /* Publish messages with QOS0, send and process Keep alive messages. */ @@ -381,6 +401,9 @@ static void prvMQTTDemoTask( void * pvParameters ) xNetworkStatus = Plaintext_FreeRTOS_Disconnect( &xNetworkContext ); configASSERT( xNetworkStatus == PLAINTEXT_TRANSPORT_SUCCESS ); + /* Reset global SUBACK status variable after completion of subscription request cycle. */ + xGlobalSubAckStatus = MQTTSubAckFailure; + /* Wait for some time between two iterations to ensure that we do not * bombard the public test mosquitto broker. */ LogInfo( ( "prvMQTTDemoTask() completed an iteration successfully. Total free heap is %u.\r\n", xPortGetFreeHeapSize() ) ); @@ -391,6 +414,51 @@ static void prvMQTTDemoTask( void * pvParameters ) } /*-----------------------------------------------------------*/ +static PlaintextTransportStatus_t prvConnectToServerWithBackoffRetries( NetworkContext_t * pNetworkContext ) +{ + PlaintextTransportStatus_t xNetworkStatus; + RetryUtilsStatus_t xRetryUtilsStatus = RetryUtilsSuccess; + RetryUtilsParams_t xReconnectParams; + + /* Initialize reconnect attempts and interval. */ + xReconnectParams.maxRetryAttempts = MAX_RETRY_ATTEMPTS; + RetryUtils_ParamsReset( &xReconnectParams ); + + /* Attempt to connect to MQTT broker. If connection fails, retry after + * a timeout. Timeout value will exponentially increase till maximum + * attempts are reached. + */ + do + { + /* Establish a TCP connection with the MQTT broker. This example connects to + * the MQTT broker as specified in democonfigMQTT_BROKER_ENDPOINT and + * democonfigMQTT_BROKER_PORT at the top of this file. */ + LogInfo( ( "Create a TCP connection to %s:%d.", + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT ) ); + xNetworkStatus = Plaintext_FreeRTOS_Connect( pNetworkContext, + democonfigMQTT_BROKER_ENDPOINT, + democonfigMQTT_BROKER_PORT, + TRANSPORT_SEND_RECV_TIMEOUT_MS, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( xNetworkStatus != PLAINTEXT_TRANSPORT_SUCCESS ) + { + LogWarn( ( "Connection to the broker failed. Retrying connection with backoff and jitter." ) ); + xRetryUtilsStatus = RetryUtils_BackoffAndSleep( &xReconnectParams ); + } + + if( xRetryUtilsStatus == RetryUtilsRetriesExhausted ) + { + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + xNetworkStatus = PLAINTEXT_TRANSPORT_CONNECT_FAILURE; + } + } while( ( xNetworkStatus != PLAINTEXT_TRANSPORT_SUCCESS ) && ( xRetryUtilsStatus == RetryUtilsSuccess ) ); + + return xNetworkStatus; +} +/*-----------------------------------------------------------*/ + static void prvCreateMQTTConnectionWithBroker( MQTTContext_t * pxMQTTContext, NetworkContext_t * pxNetworkContext ) { @@ -414,7 +482,7 @@ static void prvCreateMQTTConnectionWithBroker( MQTTContext_t * pxMQTTContext, configASSERT( xResult == MQTTSuccess ); /* Many fields not used in this demo so start with everything at 0. */ - memset( ( void * ) &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); + ( void ) memset( ( void * ) &xConnectInfo, 0x00, sizeof( xConnectInfo ) ); /* Start with a clean session i.e. direct the MQTT broker to discard any * previous session data. Also, establishing a connection with clean session @@ -440,43 +508,90 @@ static void prvCreateMQTTConnectionWithBroker( MQTTContext_t * pxMQTTContext, NULL, mqttexampleCONNACK_RECV_TIMEOUT_MS, &xSessionPresent ); - - if( xResult != MQTTSuccess ) - { - LogError( ( "Connection with MQTT broker failed.\r\n" ) ); - } + configASSERT( xResult == MQTTSuccess ); } /*-----------------------------------------------------------*/ -static void prvMQTTSubscribeToTopic( MQTTContext_t * pxMQTTContext ) +static void prvUpdateSubAckStatus( MQTTPacketInfo_t * pxPacketInfo ) { - MQTTStatus_t xResult; - MQTTSubscribeInfo_t xMQTTSubscription[ 1 ]; + MQTTStatus_t xResult = MQTTSuccess; + uint8_t * pucPayload = NULL; + size_t ulSize = 0; - /*** - * For readability, error handling in this function is restricted to the use of - * asserts(). - ***/ + xResult = MQTT_GetSubAckStatusCodes( pxPacketInfo, &pucPayload, &ulSize ); + + /* MQTT_GetSubAckStatusCodes always returns success if called with packet info + * from the event callback and non-NULL parameters. */ + configASSERT( xResult == MQTTSuccess ); + + /* Demo only subscribes to one topic, so only one status code is returned. */ + xGlobalSubAckStatus = pucPayload[ 0 ]; +} +/*-----------------------------------------------------------*/ + +static void prvMQTTSubscribeWithBackoffRetries( MQTTContext_t * pxMQTTContext ) +{ + MQTTStatus_t xResult = MQTTSuccess; + RetryUtilsStatus_t xRetryUtilsStatus = RetryUtilsSuccess; + RetryUtilsParams_t xRetryParams; /* Some fields not used by this demo so start with everything at 0. */ - ( void ) memset( ( void * ) &xMQTTSubscription, 0x00, sizeof( xMQTTSubscription ) ); - - /* Subscribe to the mqttexampleTOPIC topic filter. This example subscribes to - * only one topic and uses QOS0. */ - xMQTTSubscription[ 0 ].qos = MQTTQoS0; - xMQTTSubscription[ 0 ].pTopicFilter = mqttexampleTOPIC; - xMQTTSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( mqttexampleTOPIC ); + ( void ) memset( ( void * ) &xGlobalSubscribeInfo, 0x00, sizeof( MQTTSubscribeInfo_t ) ); /* Get a unique packet id. */ usSubscribePacketIdentifier = MQTT_GetPacketId( pxMQTTContext ); - /* Send SUBSCRIBE packet. */ - xResult = MQTT_Subscribe( pxMQTTContext, - xMQTTSubscription, - sizeof( xMQTTSubscription ) / sizeof( MQTTSubscribeInfo_t ), - usSubscribePacketIdentifier ); + /* Subscribe to the mqttexampleTOPIC topic filter. This example subscribes to + * only one topic and uses QOS0. */ + xGlobalSubscribeInfo.qos = MQTTQoS0; + xGlobalSubscribeInfo.pTopicFilter = mqttexampleTOPIC; + xGlobalSubscribeInfo.topicFilterLength = ( uint16_t ) strlen( mqttexampleTOPIC ); - configASSERT( xResult == MQTTSuccess ); + /* Initialize retry attempts and interval. */ + xRetryParams.maxRetryAttempts = MAX_RETRY_ATTEMPTS; + RetryUtils_ParamsReset( &xRetryParams ); + + do + { + /* The client is now connected to the broker. Subscribe to the topic + * as specified in mqttexampleTOPIC at the top of this file by sending a + * subscribe packet then waiting for a subscribe acknowledgment (SUBACK). + * This client will then publish to the same topic it subscribed to, so it + * will expect all the messages it sends to the broker to be sent back to it + * from the broker. This demo uses QOS0 in Subscribe, therefore, the Publish + * messages received from the broker will have QOS0. */ + LogInfo( ( "Attempt to subscribe to the MQTT topic %s.\r\n", mqttexampleTOPIC ) ); + xResult = MQTT_Subscribe( pxMQTTContext, + &xGlobalSubscribeInfo, + sizeof( xGlobalSubscribeInfo ) / sizeof( MQTTSubscribeInfo_t ), + usSubscribePacketIdentifier ); + configASSERT( xResult == MQTTSuccess ); + + LogInfo( ( "SUBSCRIBE sent for topic %s to broker.\n\n", mqttexampleTOPIC ) ); + + /* Process incoming packet from the broker. After sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Therefore, + * call generic incoming packet processing function. Since this demo is + * subscribing to the topic to which no one is publishing, probability of + * receiving Publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses the generic packet + * processing function everywhere to highlight this fact. */ + xResult = MQTT_ProcessLoop( pxMQTTContext, mqttexamplePROCESS_LOOP_TIMEOUT_MS ); + configASSERT( xResult == MQTTSuccess ); + + /* Check if recent subscription request has been rejected. #xGlobalSubAckStatus is updated + * in eventCallback to reflect the status of the SUBACK sent by the broker. It represents + * either the QoS level granted by the server upon subscription, or acknowledgement of + * server rejection of the subscription request. */ + if( xGlobalSubAckStatus == MQTTSubAckFailure ) + { + LogWarn( ( "Server rejected subscription request. Attempting to re-subscribe to topic %s.", + mqttexampleTOPIC ) ); + xRetryUtilsStatus = RetryUtils_BackoffAndSleep( &xRetryParams ); + } + + configASSERT( xRetryUtilsStatus != RetryUtilsRetriesExhausted ); + } while( ( xGlobalSubAckStatus == MQTTSubAckFailure ) && ( xRetryUtilsStatus == RetryUtilsSuccess ) ); } /*-----------------------------------------------------------*/ @@ -485,7 +600,6 @@ static void prvMQTTPublishToTopic( MQTTContext_t * pxMQTTContext ) MQTTStatus_t xResult; MQTTPublishInfo_t xMQTTPublishInfo; - /*** * For readability, error handling in this function is restricted to the use of * asserts(). @@ -494,7 +608,7 @@ static void prvMQTTPublishToTopic( MQTTContext_t * pxMQTTContext ) /* Some fields not used by this demo so start with everything at 0. */ ( void ) memset( ( void * ) &xMQTTPublishInfo, 0x00, sizeof( xMQTTPublishInfo ) ); - /* This demo uses QOS0 */ + /* This demo uses QOS0. */ xMQTTPublishInfo.qos = MQTTQoS0; xMQTTPublishInfo.retain = false; xMQTTPublishInfo.pTopicName = mqttexampleTOPIC; @@ -512,25 +626,16 @@ static void prvMQTTPublishToTopic( MQTTContext_t * pxMQTTContext ) static void prvMQTTUnsubscribeFromTopic( MQTTContext_t * pxMQTTContext ) { MQTTStatus_t xResult; - MQTTSubscribeInfo_t xMQTTSubscription[ 1 ]; - /* Some fields not used by this demo so start with everything at 0. */ - memset( ( void * ) &xMQTTSubscription, 0x00, sizeof( xMQTTSubscription ) ); - - /* Unsubscribe to the mqttexampleTOPIC topic filter. */ - xMQTTSubscription[ 0 ].qos = MQTTQoS0; - xMQTTSubscription[ 0 ].pTopicFilter = mqttexampleTOPIC; - xMQTTSubscription[ 0 ].topicFilterLength = ( uint16_t ) strlen( mqttexampleTOPIC ); - - /* Get next unique packet identifier */ + /* Get next unique packet identifier. */ usUnsubscribePacketIdentifier = MQTT_GetPacketId( pxMQTTContext ); - /* Make sure the packet id obtained is valid. */ - configASSERT( usUnsubscribePacketIdentifier != 0 ); - /* Send UNSUBSCRIBE packet. */ + /* Send UNSUBSCRIBE packet. Note that because #xGlobalSubscribeInfo + * was initialized before sending the SUBSCRIBE packet, there is no need + * to initialize it again. */ xResult = MQTT_Unsubscribe( pxMQTTContext, - xMQTTSubscription, - sizeof( xMQTTSubscription ) / sizeof( MQTTSubscribeInfo_t ), + &xGlobalSubscribeInfo, + sizeof( xGlobalSubscribeInfo ) / sizeof( MQTTSubscribeInfo_t ), usUnsubscribePacketIdentifier ); configASSERT( xResult == MQTTSuccess ); @@ -543,7 +648,20 @@ static void prvMQTTProcessResponse( MQTTPacketInfo_t * pxIncomingPacket, switch( pxIncomingPacket->type ) { case MQTT_PACKET_TYPE_SUBACK: - LogInfo( ( "Subscribed to the topic %s.\r\n", mqttexampleTOPIC ) ); + + /* A SUBACK from the broker, containing the server response to our subscription request, has been received. + * It contains the status code indicating server approval/rejection for the subscription to the single topic + * requested. The SUBACK will be parsed to obtain the status code, and this status code will be stored in global + * variable globalSubAckStatus. */ + prvUpdateSubAckStatus( pxIncomingPacket ); + + if( xGlobalSubAckStatus != MQTTSubAckFailure ) + { + LogInfo( ( "Subscribed to the topic %s with maximum QoS %u.\r\n", + mqttexampleTOPIC, + xGlobalSubAckStatus ) ); + } + /* Make sure ACK packet identifier matches with Request packet identifier. */ configASSERT( usSubscribePacketIdentifier == usPacketId ); break; diff --git a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj index e427960d84..2aa2480671 100644 --- a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj +++ b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj @@ -159,6 +159,7 @@ + @@ -193,6 +194,7 @@ + diff --git a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj.filters b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj.filters index 1e7173713d..24fe1db533 100644 --- a/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj.filters +++ b/FreeRTOS-Plus/Demo/FreeRTOS-IoT-Libraries-LTS-Beta2/mqtt/mqtt_plain_text/WIN32.vcxproj.filters @@ -126,6 +126,9 @@ FreeRTOS+\FreeRTOS IoT Libraries\platform + + FreeRTOS+\FreeRTOS IoT Libraries\platform + @@ -216,6 +219,9 @@ FreeRTOS+\FreeRTOS IoT Libraries\platform + + FreeRTOS+\FreeRTOS IoT Libraries\platform + FreeRTOS+\FreeRTOS IoT Libraries\platform diff --git a/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/freertos/retry_utils/retry_utils_freertos.c b/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/freertos/retry_utils/retry_utils_freertos.c new file mode 100644 index 0000000000..93a6e12964 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/freertos/retry_utils/retry_utils_freertos.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file retry_utils_freertos.c + * @brief Utility implementation of backoff logic, used for attempting retries of failed processes. + */ + +/* Standard includes. */ +#include + +/* Kernel includes. */ +#include "FreeRTOS.h" +#include "task.h" + +#include "retry_utils.h" + +#define _MILLISECONDS_PER_SECOND ( 1000U ) /**< @brief Milliseconds per second. */ + +extern UBaseType_t uxRand( void ); + +/*-----------------------------------------------------------*/ + +RetryUtilsStatus_t RetryUtils_BackoffAndSleep( RetryUtilsParams_t * pRetryParams ) +{ + RetryUtilsStatus_t status = RetryUtilsRetriesExhausted; + int32_t backOffDelayMs = 0; + + /* If pRetryParams->maxRetryAttempts is set to 0, try forever. */ + if( ( pRetryParams->attemptsDone < pRetryParams->maxRetryAttempts ) || + ( 0 == pRetryParams->maxRetryAttempts ) ) + { + /* Choose a random value for back-off time between 0 and the max jitter value. */ + backOffDelayMs = uxRand() % pRetryParams->nextJitterMax; + + /* Wait for backoff time to expire for the next retry. */ + vTaskDelay( pdMS_TO_TICKS( backOffDelayMs * _MILLISECONDS_PER_SECOND ) ); + + /* Increment backoff counts. */ + pRetryParams->attemptsDone++; + + /* Double the max jitter value for the next retry attempt, only + * if the new value will be less than the max backoff time value. */ + if( pRetryParams->nextJitterMax < ( MAX_RETRY_BACKOFF_SECONDS / 2U ) ) + { + pRetryParams->nextJitterMax += pRetryParams->nextJitterMax; + } + else + { + pRetryParams->nextJitterMax = MAX_RETRY_BACKOFF_SECONDS; + } + + status = RetryUtilsSuccess; + } + else + { + /* When max retry attempts are exhausted, let application know by + * returning RetryUtilsRetriesExhausted. Application may choose to + * restart the retry process after calling RetryUtils_ParamsReset(). */ + status = RetryUtilsRetriesExhausted; + RetryUtils_ParamsReset( pRetryParams ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +void RetryUtils_ParamsReset( RetryUtilsParams_t * pRetryParams ) +{ + uint32_t jitter = 0; + + /* Reset attempts done to zero so that the next retry cycle can start. */ + pRetryParams->attemptsDone = 0; + + /* Calculate jitter value using picking a random number. */ + jitter = ( uxRand() % MAX_JITTER_VALUE_SECONDS ); + + /* Reset the backoff value to the initial time out value plus jitter. */ + pRetryParams->nextJitterMax = INITIAL_RETRY_BACKOFF_SECONDS + jitter; +} + +/*-----------------------------------------------------------*/ diff --git a/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/include/retry_utils.h b/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/include/retry_utils.h new file mode 100644 index 0000000000..29a233fe94 --- /dev/null +++ b/FreeRTOS-Plus/Source/FreeRTOS-IoT-Libraries-LTS-Beta2/c_sdk/platform/include/retry_utils.h @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file retry_utils.h + * @brief Declaration of the exponential backoff retry logic utility functions + * and constants. + */ + +#ifndef RETRY_UTILS_H_ +#define RETRY_UTILS_H_ + +/* Standard include. */ +#include + +/** + * @page retryutils_page Retry Utilities + * @brief An abstraction of utilities for retrying with exponential back off and + * jitter. + * + * @section retryutils_overview Overview + * The retry utilities are a set of APIs that aid in retrying with exponential + * backoff and jitter. Exponential backoff with jitter is strongly recommended + * for retrying failed actions over the network with servers. Please see + * https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ for + * more information about the benefits with AWS. + * + * Exponential backoff with jitter is typically used when retrying a failed + * connection to the server. In an environment with poor connectivity, a client + * can get disconnected at any time. A backoff strategy helps the client to + * conserve battery by not repeatedly attempting reconnections when they are + * unlikely to succeed. + * + * Before retrying the failed communication to the server there is a quiet period. + * In this quiet period, the task that is retrying must sleep for some random + * amount of seconds between 0 and the lesser of a base value and a predefined + * maximum. The base is doubled with each retry attempt until the maximum is + * reached.
+ * + * > sleep_seconds = random_between( 0, min( 2attempts_count * base_seconds, maximum_seconds ) ) + * + * @section retryutils_implementation Implementing Retry Utils + * + * The functions that must be implemented are:
+ * - @ref RetryUtils_ParamsReset + * - @ref RetryUtils_BackoffAndSleep + * + * The functions are used as shown in the diagram below. This is the exponential + * backoff with jitter loop: + * + * @image html retry_utils_flow.png width=25% + * + * The following steps give guidance on implementing the Retry Utils. An example + * implementation of the Retry Utils for a POSIX platform can be found in file + * @ref retry_utils_posix.c. + * + * -# Implementing @ref RetryUtils_ParamsReset + * @snippet this define_retryutils_paramsreset + *
+ * This function initializes @ref RetryUtilsParams_t. It is expected to set + * @ref RetryUtilsParams_t.attemptsDone to zero. It is also expected to set + * @ref RetryUtilsParams_t.nextJitterMax to @ref INITIAL_RETRY_BACKOFF_SECONDS + * plus some random amount of seconds, jitter. This jitter is a random number + * between 0 and @ref MAX_JITTER_VALUE_SECONDS. This function must be called + * before entering the exponential backoff with jitter loop using + * @ref RetryUtils_BackoffAndSleep.

+ * Please follow the example below to implement your own @ref RetryUtils_ParamsReset. + * The lines with FIXME comments should be updated. + * @code{c} + * void RetryUtils_ParamsReset( RetryUtilsParams_t * pRetryParams ) + * { + * uint32_t jitter = 0; + * + * // Reset attempts done to zero so that the next retry cycle can start. + * pRetryParams->attemptsDone = 0; + * + * // Seed pseudo random number generator with the current time. FIXME: Your + * // system may have another method to retrieve the current time to seed the + * // pseudo random number generator. + * srand( time( NULL ) ); + * + * // Calculate jitter value using picking a random number. + * jitter = ( rand() % MAX_JITTER_VALUE_SECONDS ); + * + * // Reset the backoff value to the initial time out value plus jitter. + * pRetryParams->nextJitterMax = INITIAL_RETRY_BACKOFF_SECONDS + jitter; + * } + * @endcode
+ * + * -# Implementing @ref RetryUtils_BackoffAndSleep + * @snippet this define_retryutils_backoffandsleep + *
+ * When this function is invoked, the calling task is expected to sleep a random + * number of seconds between 0 and @ref RetryUtilsParams_t.nextJitterMax. After + * sleeping this function must double @ref RetryUtilsParams_t.nextJitterMax, but + * not exceeding @ref MAX_RETRY_BACKOFF_SECONDS. When @ref RetryUtilsParams_t.maxRetryAttempts + * are reached this function should return @ref RetryUtilsRetriesExhausted, unless + * @ref RetryUtilsParams_t.maxRetryAttempts is set to zero. + * When @ref RetryUtilsRetriesExhausted is returned the calling application can + * stop trying with a failure, or it can call @ref RetryUtils_ParamsReset again + * and restart the exponential back off with jitter loop.

+ * Please follow the example below to implement your own @ref RetryUtils_BackoffAndSleep. + * The lines with FIXME comments should be updated. + * @code{c} + * RetryUtilsStatus_t RetryUtils_BackoffAndSleep( RetryUtilsParams_t * pRetryParams ) + * { + * RetryUtilsStatus_t status = RetryUtilsRetriesExhausted; + * // The quiet period delay in seconds. + * int backOffDelay = 0; + * + * // If pRetryParams->maxRetryAttempts is set to 0, try forever. + * if( ( pRetryParams->attemptsDone < pRetryParams->maxRetryAttempts ) || + * ( 0 == pRetryParams->maxRetryAttempts ) ) + * { + * // Choose a random value for back-off time between 0 and the max jitter value. + * backOffDelay = rand() % pRetryParams->nextJitterMax; + * + * // Wait for backoff time to expire for the next retry. + * ( void ) myThreadSleepFunction( backOffDelay ); // FIXME: Replace with your system's thread sleep function. + * + * // Increment backoff counts. + * pRetryParams->attemptsDone++; + * + * // Double the max jitter value for the next retry attempt, only + * // if the new value will be less than the max backoff time value. + * if( pRetryParams->nextJitterMax < ( MAX_RETRY_BACKOFF_SECONDS / 2U ) ) + * { + * pRetryParams->nextJitterMax += pRetryParams->nextJitterMax; + * } + * else + * { + * pRetryParams->nextJitterMax = MAX_RETRY_BACKOFF_SECONDS; + * } + * + * status = RetryUtilsSuccess; + * } + * else + * { + * // When max retry attempts are exhausted, let application know by + * // returning RetryUtilsRetriesExhausted. Application may choose to + * // restart the retry process after calling RetryUtils_ParamsReset(). + * status = RetryUtilsRetriesExhausted; + * RetryUtils_ParamsReset( pRetryParams ); + * } + * + * return status; + * } + * @endcode + */ + +/** + * @brief Max number of retry attempts. Set this value to 0 if the client must + * retry forever. + */ +#define MAX_RETRY_ATTEMPTS 4U + +/** + * @brief Initial fixed backoff value in seconds between two successive + * retries. A random jitter value is added to every backoff value. + */ +#define INITIAL_RETRY_BACKOFF_SECONDS 1U + +/** + * @brief Max backoff value in seconds. + */ +#define MAX_RETRY_BACKOFF_SECONDS 128U + +/** + * @brief Max jitter value in seconds. + */ +#define MAX_JITTER_VALUE_SECONDS 5U + +/** + * @brief Status for @ref RetryUtils_BackoffAndSleep. + */ +typedef enum RetryUtilsStatus +{ + RetryUtilsSuccess = 0, /**< @brief The function returned successfully after sleeping. */ + RetryUtilsRetriesExhausted /**< @brief The function exhausted all retry attempts. */ +} RetryUtilsStatus_t; + +/** + * @brief Represents parameters required for retry logic. + */ +typedef struct RetryUtilsParams +{ + /** + * @brief Max number of retry attempts. Set this value to 0 if the client must + * retry forever. + */ + uint32_t maxRetryAttempts; + + /** + * @brief The cumulative count of backoff delay cycles completed + * for retries. + */ + uint32_t attemptsDone; + + /** + * @brief The max jitter value for backoff time in retry attempt. + */ + uint32_t nextJitterMax; +} RetryUtilsParams_t; + + +/** + * @brief Resets the retry timeout value and number of attempts. + * This function must be called by the application before a new retry attempt. + * + * @param[in, out] pRetryParams Structure containing attempts done and timeout + * value. + */ +void RetryUtils_ParamsReset( RetryUtilsParams_t * pRetryParams ); + +/** + * @brief Simple platform specific exponential backoff function. The application + * must use this function between retry failures to add exponential delay. + * This function will block the calling task for the current timeout value. + * + * @param[in, out] pRetryParams Structure containing retry parameters. + * + * @return #RetryUtilsSuccess after a successful sleep, #RetryUtilsRetriesExhausted + * when all attempts are exhausted. + */ +RetryUtilsStatus_t RetryUtils_BackoffAndSleep( RetryUtilsParams_t * pRetryParams ); + +#endif /* ifndef RETRY_UTILS_H_ */