1 /**************************************************************************/
2 /*                                                                        */
3 /*       Copyright (c) Microsoft Corporation. All rights reserved.        */
4 /*                                                                        */
5 /*       This software is licensed under the Microsoft Software License   */
6 /*       Terms for Microsoft Azure RTOS. Full text of the license can be  */
7 /*       found in the LICENSE file at https://aka.ms/AzureRTOS_EULA       */
8 /*       and in the root directory of this software.                      */
9 /*                                                                        */
10 /**************************************************************************/
11 
12 #include <stdio.h>
13 
14 #include "nx_azure_iot_hub_client.h"
15 
16 #ifndef SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC
17 #define SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC                           (10 * 60)
18 #endif /* SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC */
19 
20 #ifndef SAMPLE_INITIAL_EXPONENTIAL_BACKOFF_IN_SEC
21 #define SAMPLE_INITIAL_EXPONENTIAL_BACKOFF_IN_SEC                       (3)
22 #endif /* SAMPLE_INITIAL_EXPONENTIAL_BACKOFF_IN_SEC */
23 
24 #ifndef SAMPLE_MAX_EXPONENTIAL_BACKOFF_JITTER_PERCENT
25 #define SAMPLE_MAX_EXPONENTIAL_BACKOFF_JITTER_PERCENT                   (60)
26 #endif /* SAMPLE_MAX_EXPONENTIAL_BACKOFF_JITTER_PERCENT */
27 
28 VOID sample_connection_monitor(NX_IP *ip_ptr, NX_AZURE_IOT_HUB_CLIENT *iothub_client_ptr, UINT sample_status,
29                                UINT (*iothub_init)(NX_AZURE_IOT_HUB_CLIENT *hub_client_ptr));
30 
31 static UINT exponential_retry_count;
32 
33 static UINT iothub_init_count = 0;
34 
exponential_backoff_with_jitter()35 static UINT exponential_backoff_with_jitter()
36 {
37 double jitter_percent = (SAMPLE_MAX_EXPONENTIAL_BACKOFF_JITTER_PERCENT / 100.0) * (rand() / ((double)RAND_MAX));
38 UINT base_delay = SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC;
39 uint64_t delay;
40 
41     if (exponential_retry_count < (sizeof(UINT) * 8))
42     {
43         delay = (uint64_t)((1 << exponential_retry_count) * SAMPLE_INITIAL_EXPONENTIAL_BACKOFF_IN_SEC);
44         if (delay <= (UINT)(-1))
45         {
46             base_delay = (UINT)delay;
47         }
48     }
49 
50     if (base_delay > SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC)
51     {
52         base_delay = SAMPLE_MAX_EXPONENTIAL_BACKOFF_IN_SEC;
53     }
54     else
55     {
56         exponential_retry_count++;
57     }
58 
59     return((UINT)(base_delay * (1 + jitter_percent)) * NX_IP_PERIODIC_RATE) ;
60 }
61 
exponential_backoff_reset()62 static VOID exponential_backoff_reset()
63 {
64     exponential_retry_count = 0;
65 }
66 
67 /**
68  *
69  * Connection state
70  *
71  *
72  * +--------------+             +--------------+                      +--------------+
73  * |              |    INIT     |              |       NETWORK        |              |
74  * |              |   SUCCESS   |              |        GOOD          |              |
75  * |    INIT      |             |   NETWORK    |                      |   CONNECT    |
76  * |              +------------->    CHECK      +--------------------->              +---------+
77  * |              |             |              |                      |              |         |
78  * |              |             |              |                      |              |         |
79  * +------^-------+             +------^-------+                      +------+-------+         |
80  *        |                            |                                     |                 |
81  *        |                            |                        CONNECT FAIL |                 |
82  *        |                            |          +--------------------------+                 |
83  *        |                            |          |                                  CONNECTED |
84  *        |                  RECONNECT |          |                                            |
85  *        |                            |          |                                            |
86  *        |                            |   +------v-------+              +--------------+      |
87  *        | REINITIALIZE               |   |              |              |              |      |
88  *        |                            +---+              |  DISCONNECT  |              |      |
89  *        |                                | DISCONNECTED |              |   CONNECTED  |      |
90  *        |                                |              <--------------+              <------+
91  *        +--------------------------------+              |              |              |
92  *                                         |              |              |              |
93  *                                         +--------------+              +---^------+---+
94  *                                                                           |      |(TELEMETRY |
95  *                                                                           |      | C2D |
96  *                                                                           |      | COMMAND | METHOD |
97  *                                                                           +------+ PROPERTIES | DEVICE TWIN)
98  *
99  *
100  *
101  */
sample_connection_monitor(NX_IP * ip_ptr,NX_AZURE_IOT_HUB_CLIENT * hub_client_ptr,UINT connection_status,UINT (* iothub_init)(NX_AZURE_IOT_HUB_CLIENT * hub_client_ptr))102 VOID sample_connection_monitor(NX_IP *ip_ptr, NX_AZURE_IOT_HUB_CLIENT *hub_client_ptr, UINT connection_status,
103                                UINT (*iothub_init)(NX_AZURE_IOT_HUB_CLIENT *hub_client_ptr))
104 {
105 UINT loop = NX_TRUE;
106 ULONG gateway_address;
107 
108     /* Check parameters.  */
109     if ((ip_ptr == NX_NULL) || (hub_client_ptr == NX_NULL) || (iothub_init == NX_NULL))
110     {
111         return;
112     }
113 
114     /* Check if connected.  */
115     if (connection_status == NX_SUCCESS)
116     {
117 
118         /* Reset the exponential.  */
119         exponential_backoff_reset();
120     }
121     else
122     {
123 
124         /* Disconnect.  */
125         if (connection_status != NX_AZURE_IOT_NOT_INITIALIZED)
126         {
127             nx_azure_iot_hub_client_disconnect(hub_client_ptr);
128         }
129 
130         /* Recover.  */
131         while (loop)
132         {
133             switch (connection_status)
134             {
135 
136                 /* Something bad has happened with client state, we need to re-initialize it.  */
137                 case NX_DNS_QUERY_FAILED :
138                 case NXD_MQTT_ERROR_BAD_USERNAME_PASSWORD :
139                 case NXD_MQTT_ERROR_NOT_AUTHORIZED :
140                 {
141 
142                     /* Deinitialize iot hub client. */
143                     nx_azure_iot_hub_client_deinitialize(hub_client_ptr);
144                 }
145                 /* fallthrough */
146 
147                 case NX_AZURE_IOT_NOT_INITIALIZED:
148                 {
149                     if (iothub_init_count++)
150                     {
151                         printf("Re-initializing iothub connection, after backoff\r\n");
152                         tx_thread_sleep(exponential_backoff_with_jitter());
153                     }
154 
155                     /* Initialize iot hub.  */
156                     if (iothub_init(hub_client_ptr))
157                     {
158                         connection_status = NX_AZURE_IOT_NOT_INITIALIZED;
159                     }
160                     else
161                     {
162 
163                         /* Wait for network.  */
164                         while (nx_ip_gateway_address_get(ip_ptr, &gateway_address))
165                         {
166                             tx_thread_sleep(NX_IP_PERIODIC_RATE);
167                         }
168 
169                         /* Connect to iot hub.  */
170                         connection_status = nx_azure_iot_hub_client_connect(hub_client_ptr, NX_FALSE, NX_WAIT_FOREVER);
171                     }
172                 }
173                 break;
174 
175                 case NX_AZURE_IOT_SAS_TOKEN_EXPIRED:
176                 {
177                     printf("SAS token expired\r\n");
178                 }
179                 /* fallthrough */
180 
181                 default :
182                 {
183                     printf("Reconnecting iothub, after backoff\r\n");
184 
185                     tx_thread_sleep(exponential_backoff_with_jitter());
186 
187                     /* Wait for network.  */
188                     while (nx_ip_gateway_address_get(ip_ptr, &gateway_address))
189                     {
190                         tx_thread_sleep(NX_IP_PERIODIC_RATE);
191                     }
192 
193                     connection_status = nx_azure_iot_hub_client_connect(hub_client_ptr, NX_FALSE, NX_WAIT_FOREVER);
194                 }
195                 break;
196             }
197 
198             /* Check status.  */
199             if (connection_status == NX_SUCCESS)
200             {
201                 return;
202             }
203         }
204     }
205 }
206