1 /*
2  * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 #include <sys/param.h>
9 
10 #include <freertos/FreeRTOS.h>
11 #include <freertos/semphr.h>
12 #include <freertos/task.h>
13 
14 #include <cJSON.h>
15 
16 #include <esp_log.h>
17 #include <esp_err.h>
18 #include <esp_wifi.h>
19 #include <esp_timer.h>
20 
21 #include <protocomm.h>
22 #include <protocomm_security0.h>
23 #include <protocomm_security1.h>
24 
25 #include "wifi_provisioning_priv.h"
26 
27 #define WIFI_PROV_MGR_VERSION      "v1.1"
28 #define WIFI_PROV_STORAGE_BIT       BIT0
29 #define WIFI_PROV_SETTING_BIT       BIT1
30 #define MAX_SCAN_RESULTS           CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES
31 
32 #define ACQUIRE_LOCK(mux)     assert(xSemaphoreTake(mux, portMAX_DELAY) == pdTRUE)
33 #define RELEASE_LOCK(mux)     assert(xSemaphoreGive(mux) == pdTRUE)
34 
35 static const char *TAG = "wifi_prov_mgr";
36 
37 ESP_EVENT_DEFINE_BASE(WIFI_PROV_EVENT);
38 
39 typedef enum {
40     WIFI_PROV_STATE_IDLE,
41     WIFI_PROV_STATE_STARTING,
42     WIFI_PROV_STATE_STARTED,
43     WIFI_PROV_STATE_CRED_RECV,
44     WIFI_PROV_STATE_FAIL,
45     WIFI_PROV_STATE_SUCCESS,
46     WIFI_PROV_STATE_STOPPING
47 } wifi_prov_mgr_state_t;
48 
49 /**
50  * @brief  Structure for storing capabilities supported by
51  *         the provisioning service
52  */
53 struct wifi_prov_capabilities {
54     /* Security 0 is used */
55     bool no_sec;
56 
57     /* Proof of Possession is not required for establishing session */
58     bool no_pop;
59 
60     /* Provisioning doesn't stop on it's own after receiving Wi-Fi credentials
61      * instead application has to explicitly call wifi_prov_mgr_stop_provisioning() */
62     bool no_auto_stop;
63 };
64 
65 /**
66  * @brief  Structure for storing miscellaneous information about
67  *         provisioning service that will be conveyed to clients
68  */
69 struct wifi_prov_info {
70     const char *version;
71     struct wifi_prov_capabilities capabilities;
72 };
73 
74 /**
75  * @brief  Context data for provisioning manager
76  */
77 struct wifi_prov_mgr_ctx {
78     /* Provisioning manager configuration */
79     wifi_prov_mgr_config_t mgr_config;
80 
81     /* State of the provisioning service */
82     wifi_prov_mgr_state_t prov_state;
83 
84     /* Provisioning scheme configuration */
85     void *prov_scheme_config;
86 
87     /* Protocomm handle */
88     protocomm_t *pc;
89 
90     /* Type of security to use with protocomm */
91     int security;
92 
93     /* Pointer to proof of possession */
94     protocomm_security_pop_t pop;
95 
96     /* Handle for Provisioning Auto Stop timer */
97     esp_timer_handle_t autostop_timer;
98 
99     /* Handle for delayed Wi-Fi connection timer */
100     esp_timer_handle_t wifi_connect_timer;
101 
102     /* State of Wi-Fi Station */
103     wifi_prov_sta_state_t wifi_state;
104 
105     /* Code for Wi-Fi station disconnection (if disconnected) */
106     wifi_prov_sta_fail_reason_t wifi_disconnect_reason;
107 
108     /* Protocomm handlers for Wi-Fi configuration endpoint */
109     wifi_prov_config_handlers_t *wifi_prov_handlers;
110 
111     /* Protocomm handlers for Wi-Fi scan endpoint */
112     wifi_prov_scan_handlers_t *wifi_scan_handlers;
113 
114     /* Count of used endpoint UUIDs */
115     unsigned int endpoint_uuid_used;
116 
117     /* Provisioning service information */
118     struct wifi_prov_info mgr_info;
119 
120     /* Application related information in JSON format */
121     cJSON *app_info_json;
122 
123     /* Delay after which resources will be cleaned up asynchronously
124      * upon execution of wifi_prov_mgr_stop_provisioning() */
125     uint32_t cleanup_delay;
126 
127     /* Wi-Fi scan parameters and state variables */
128     bool scanning;
129     uint8_t channels_per_group;
130     uint16_t curr_channel;
131     uint16_t ap_list_len[14];   // 14 entries corresponding to each channel
132     wifi_ap_record_t *ap_list[14];
133     wifi_ap_record_t *ap_list_sorted[MAX_SCAN_RESULTS];
134     wifi_scan_config_t scan_cfg;
135 };
136 
137 /* Mutex to lock/unlock access to provisioning singleton
138  * context data. This is allocated only once on first init
139  * and never deleted as wifi_prov_mgr is a singleton */
140 static SemaphoreHandle_t prov_ctx_lock = NULL;
141 
142 /* Pointer to provisioning context data */
143 static struct wifi_prov_mgr_ctx *prov_ctx;
144 
145 /* This executes registered app_event_callback for a particular event
146  *
147  * NOTE : By the time this fucntion returns, it is possible that
148  * the manager got de-initialized due to a call to wifi_prov_mgr_deinit()
149  * either inside the event callbacks or from another thread. Therefore
150  * post execution of execute_event_cb(), the validity of prov_ctx must
151  * always be checked. A cleaner way, to avoid this pitfall safely, would
152  * be to limit the usage of this function to only public APIs, and that
153  * too at the very end, just before returning.
154  *
155  * NOTE: This function should be called only after ensuring that the
156  * context is valid and the control mutex is locked. */
execute_event_cb(wifi_prov_cb_event_t event_id,void * event_data,size_t event_data_size)157 static void execute_event_cb(wifi_prov_cb_event_t event_id, void *event_data, size_t event_data_size)
158 {
159     ESP_LOGD(TAG, "execute_event_cb : %d", event_id);
160 
161     if (prov_ctx) {
162         wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb;
163         void *app_data = prov_ctx->mgr_config.app_event_handler.user_data;
164 
165         wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb;
166         void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data;
167 
168         /* Release the mutex before executing the callbacks. This is done so that
169          * wifi_prov_mgr_event_handler() doesn't stay blocked for the duration */
170         RELEASE_LOCK(prov_ctx_lock);
171 
172         if (scheme_cb) {
173             /* Call scheme specific event handler */
174             scheme_cb(scheme_data, event_id, event_data);
175         }
176 
177         if (app_cb) {
178             /* Call application specific event handler */
179             app_cb(app_data, event_id, event_data);
180         }
181 
182         if (esp_event_post(WIFI_PROV_EVENT, event_id,
183                            event_data, event_data_size,
184                            portMAX_DELAY) != ESP_OK) {
185             ESP_LOGE(TAG, "Failed to post event %d to default event loop", event_id);
186         }
187 
188         ACQUIRE_LOCK(prov_ctx_lock);
189     }
190 }
191 
wifi_prov_mgr_set_app_info(const char * label,const char * version,const char ** capabilities,size_t total_capabilities)192 esp_err_t wifi_prov_mgr_set_app_info(const char *label, const char *version,
193                                      const char**capabilities, size_t total_capabilities)
194 {
195     if (!label || !version || !capabilities) {
196         return ESP_ERR_INVALID_ARG;
197     }
198 
199     if (!prov_ctx_lock) {
200         ESP_LOGE(TAG, "Provisioning manager not initialized");
201         return ESP_ERR_INVALID_STATE;
202     }
203 
204     esp_err_t ret = ESP_FAIL;
205     ACQUIRE_LOCK(prov_ctx_lock);
206 
207     if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) {
208         if (!prov_ctx->app_info_json) {
209             prov_ctx->app_info_json = cJSON_CreateObject();
210         }
211 
212         cJSON *new_entry_json = cJSON_CreateObject();
213         cJSON *capabilities_json = cJSON_CreateArray();
214         cJSON_AddItemToObject(prov_ctx->app_info_json, label, new_entry_json);
215 
216         /* Version ("ver") */
217         cJSON_AddStringToObject(new_entry_json, "ver", version);
218 
219         /* List of capabilities ("cap") */
220         cJSON_AddItemToObject(new_entry_json, "cap", capabilities_json);
221         for (unsigned int i = 0; i < total_capabilities; i++) {
222             if (capabilities[i]) {
223                 cJSON_AddItemToArray(capabilities_json, cJSON_CreateString(capabilities[i]));
224             }
225         }
226         ret = ESP_OK;
227     } else {
228         ret = ESP_ERR_INVALID_STATE;
229     }
230 
231     RELEASE_LOCK(prov_ctx_lock);
232     return ret;
233 }
234 
wifi_prov_get_info_json(void)235 static cJSON* wifi_prov_get_info_json(void)
236 {
237     cJSON *full_info_json = prov_ctx->app_info_json ?
238                                 cJSON_Duplicate(prov_ctx->app_info_json, 1) : cJSON_CreateObject();
239     cJSON *prov_info_json = cJSON_CreateObject();
240     cJSON *prov_capabilities = cJSON_CreateArray();
241 
242     /* Use label "prov" to indicate provisioning related information */
243     cJSON_AddItemToObject(full_info_json, "prov", prov_info_json);
244 
245     /* Version field */
246     cJSON_AddStringToObject(prov_info_json, "ver", prov_ctx->mgr_info.version);
247 
248     /* Capabilities field */
249     cJSON_AddItemToObject(prov_info_json, "cap", prov_capabilities);
250 
251     /* If Security / Proof of Possession is not used, indicate in capabilities */
252     if (prov_ctx->mgr_info.capabilities.no_sec) {
253         cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_sec"));
254     } else if (prov_ctx->mgr_info.capabilities.no_pop) {
255         cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("no_pop"));
256     }
257 
258     /* Indicate capability for performing Wi-Fi scan */
259     cJSON_AddItemToArray(prov_capabilities, cJSON_CreateString("wifi_scan"));
260     return full_info_json;
261 }
262 
263 /* Declare the internal event handler */
264 static void wifi_prov_mgr_event_handler_internal(void* arg, esp_event_base_t event_base,
265                                                  int32_t event_id, void* event_data);
266 
wifi_prov_mgr_start_service(const char * service_name,const char * service_key)267 static esp_err_t wifi_prov_mgr_start_service(const char *service_name, const char *service_key)
268 {
269     const wifi_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme;
270     esp_err_t ret;
271 
272     /* Create new protocomm instance */
273     prov_ctx->pc = protocomm_new();
274     if (prov_ctx->pc == NULL) {
275         ESP_LOGE(TAG, "Failed to create new protocomm instance");
276         return ESP_FAIL;
277     }
278 
279     ret = scheme->set_config_service(prov_ctx->prov_scheme_config, service_name, service_key);
280     if (ret != ESP_OK) {
281         ESP_LOGE(TAG, "Failed to configure service");
282         protocomm_delete(prov_ctx->pc);
283         return ret;
284     }
285 
286     /* Start provisioning */
287     ret = scheme->prov_start(prov_ctx->pc, prov_ctx->prov_scheme_config);
288     if (ret != ESP_OK) {
289         ESP_LOGE(TAG, "Failed to start service");
290         protocomm_delete(prov_ctx->pc);
291         return ret;
292     }
293 
294     /* Set version information / capabilities of provisioning service and application */
295     cJSON *version_json = wifi_prov_get_info_json();
296     char *version_str = cJSON_Print(version_json);
297     ret = protocomm_set_version(prov_ctx->pc, "proto-ver", version_str);
298     free(version_str);
299     cJSON_Delete(version_json);
300     if (ret != ESP_OK) {
301         ESP_LOGE(TAG, "Failed to set version endpoint");
302         scheme->prov_stop(prov_ctx->pc);
303         protocomm_delete(prov_ctx->pc);
304         return ret;
305     }
306 
307     /* Set protocomm security type for endpoint */
308     if (prov_ctx->security == 0) {
309         ret = protocomm_set_security(prov_ctx->pc, "prov-session",
310                                      &protocomm_security0, NULL);
311     } else if (prov_ctx->security == 1) {
312         ret = protocomm_set_security(prov_ctx->pc, "prov-session",
313                                      &protocomm_security1, &prov_ctx->pop);
314     } else {
315         ESP_LOGE(TAG, "Unsupported protocomm security version %d", prov_ctx->security);
316         ret = ESP_ERR_INVALID_ARG;
317     }
318     if (ret != ESP_OK) {
319         ESP_LOGE(TAG, "Failed to set security endpoint");
320         scheme->prov_stop(prov_ctx->pc);
321         protocomm_delete(prov_ctx->pc);
322         return ret;
323     }
324 
325     prov_ctx->wifi_prov_handlers = malloc(sizeof(wifi_prov_config_handlers_t));
326     ret = get_wifi_prov_handlers(prov_ctx->wifi_prov_handlers);
327     if (ret != ESP_OK) {
328         ESP_LOGD(TAG, "Failed to allocate memory for provisioning handlers");
329         scheme->prov_stop(prov_ctx->pc);
330         protocomm_delete(prov_ctx->pc);
331         return ESP_ERR_NO_MEM;
332     }
333 
334     /* Add protocomm endpoint for Wi-Fi station configuration */
335     ret = protocomm_add_endpoint(prov_ctx->pc, "prov-config",
336                                  wifi_prov_config_data_handler,
337                                  prov_ctx->wifi_prov_handlers);
338     if (ret != ESP_OK) {
339         ESP_LOGE(TAG, "Failed to set provisioning endpoint");
340         free(prov_ctx->wifi_prov_handlers);
341         scheme->prov_stop(prov_ctx->pc);
342         protocomm_delete(prov_ctx->pc);
343         return ret;
344     }
345 
346     prov_ctx->wifi_scan_handlers = malloc(sizeof(wifi_prov_scan_handlers_t));
347     ret = get_wifi_scan_handlers(prov_ctx->wifi_scan_handlers);
348     if (ret != ESP_OK) {
349         ESP_LOGD(TAG, "Failed to allocate memory for Wi-Fi scan handlers");
350         free(prov_ctx->wifi_prov_handlers);
351         scheme->prov_stop(prov_ctx->pc);
352         protocomm_delete(prov_ctx->pc);
353         return ESP_ERR_NO_MEM;
354     }
355 
356     /* Add endpoint for scanning Wi-Fi APs and sending scan list */
357     ret = protocomm_add_endpoint(prov_ctx->pc, "prov-scan",
358                                  wifi_prov_scan_handler,
359                                  prov_ctx->wifi_scan_handlers);
360     if (ret != ESP_OK) {
361         ESP_LOGE(TAG, "Failed to set Wi-Fi scan endpoint");
362         free(prov_ctx->wifi_scan_handlers);
363         free(prov_ctx->wifi_prov_handlers);
364         scheme->prov_stop(prov_ctx->pc);
365         protocomm_delete(prov_ctx->pc);
366         return ret;
367     }
368 
369     /* Register global event handler */
370     ret = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
371                                      wifi_prov_mgr_event_handler_internal, NULL);
372     if (ret != ESP_OK) {
373         ESP_LOGE(TAG, "Failed to register WiFi event handler");
374         free(prov_ctx->wifi_scan_handlers);
375         free(prov_ctx->wifi_prov_handlers);
376         scheme->prov_stop(prov_ctx->pc);
377         protocomm_delete(prov_ctx->pc);
378         return ret;
379     }
380 
381     ret = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
382                                      wifi_prov_mgr_event_handler_internal, NULL);
383     if (ret != ESP_OK) {
384         ESP_LOGE(TAG, "Failed to register IP event handler");
385         esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
386                                      wifi_prov_mgr_event_handler_internal);
387         free(prov_ctx->wifi_scan_handlers);
388         free(prov_ctx->wifi_prov_handlers);
389         scheme->prov_stop(prov_ctx->pc);
390         protocomm_delete(prov_ctx->pc);
391         return ret;
392     }
393 
394     ESP_LOGI(TAG, "Provisioning started with service name : %s ",
395              service_name ? service_name : "<NULL>");
396     return ESP_OK;
397 }
398 
wifi_prov_mgr_endpoint_create(const char * ep_name)399 esp_err_t wifi_prov_mgr_endpoint_create(const char *ep_name)
400 {
401     if (!prov_ctx_lock) {
402         ESP_LOGE(TAG, "Provisioning manager not initialized");
403         return ESP_ERR_INVALID_STATE;
404     }
405 
406     esp_err_t err = ESP_FAIL;
407 
408     ACQUIRE_LOCK(prov_ctx_lock);
409     if (prov_ctx &&
410         prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) {
411         err = prov_ctx->mgr_config.scheme.set_config_endpoint(
412                 prov_ctx->prov_scheme_config, ep_name,
413                 prov_ctx->endpoint_uuid_used + 1);
414     }
415     if (err != ESP_OK) {
416         ESP_LOGE(TAG, "Failed to create additional endpoint");
417     } else {
418         prov_ctx->endpoint_uuid_used++;
419     }
420     RELEASE_LOCK(prov_ctx_lock);
421     return err;
422 }
423 
wifi_prov_mgr_endpoint_register(const char * ep_name,protocomm_req_handler_t handler,void * user_ctx)424 esp_err_t wifi_prov_mgr_endpoint_register(const char *ep_name, protocomm_req_handler_t handler, void *user_ctx)
425 {
426     if (!prov_ctx_lock) {
427         ESP_LOGE(TAG, "Provisioning manager not initialized");
428         return ESP_ERR_INVALID_STATE;
429     }
430 
431     esp_err_t err = ESP_FAIL;
432 
433     ACQUIRE_LOCK(prov_ctx_lock);
434     if (prov_ctx &&
435         prov_ctx->prov_state > WIFI_PROV_STATE_STARTING &&
436         prov_ctx->prov_state < WIFI_PROV_STATE_STOPPING) {
437         err = protocomm_add_endpoint(prov_ctx->pc, ep_name, handler, user_ctx);
438     }
439     RELEASE_LOCK(prov_ctx_lock);
440 
441     if (err != ESP_OK) {
442         ESP_LOGE(TAG, "Failed to register handler for endpoint");
443     }
444     return err;
445 }
446 
wifi_prov_mgr_endpoint_unregister(const char * ep_name)447 void wifi_prov_mgr_endpoint_unregister(const char *ep_name)
448 {
449     if (!prov_ctx_lock) {
450         ESP_LOGE(TAG, "Provisioning manager not initialized");
451         return;
452     }
453 
454     ACQUIRE_LOCK(prov_ctx_lock);
455     if (prov_ctx &&
456         prov_ctx->prov_state > WIFI_PROV_STATE_STARTING &&
457         prov_ctx->prov_state < WIFI_PROV_STATE_STOPPING) {
458         protocomm_remove_endpoint(prov_ctx->pc, ep_name);
459     }
460     RELEASE_LOCK(prov_ctx_lock);
461 }
462 
prov_stop_task(void * arg)463 static void prov_stop_task(void *arg)
464 {
465     bool is_this_a_task = (bool) arg;
466 
467     wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb;
468     void *app_data = prov_ctx->mgr_config.app_event_handler.user_data;
469 
470     wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb;
471     void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data;
472 
473     /* This delay is so that the client side app is notified first
474      * and then the provisioning is stopped. Generally 1000ms is enough. */
475     uint32_t cleanup_delay = prov_ctx->cleanup_delay > 100 ? prov_ctx->cleanup_delay : 100;
476     vTaskDelay(cleanup_delay / portTICK_PERIOD_MS);
477 
478     /* All the extra application added endpoints are also
479      * removed automatically when prov_stop is called */
480     prov_ctx->mgr_config.scheme.prov_stop(prov_ctx->pc);
481 
482     /* Delete protocomm instance */
483     protocomm_delete(prov_ctx->pc);
484     prov_ctx->pc = NULL;
485 
486     /* Free provisioning handlers */
487     free(prov_ctx->wifi_prov_handlers->ctx);
488     free(prov_ctx->wifi_prov_handlers);
489     prov_ctx->wifi_prov_handlers = NULL;
490 
491     free(prov_ctx->wifi_scan_handlers->ctx);
492     free(prov_ctx->wifi_scan_handlers);
493     prov_ctx->wifi_scan_handlers = NULL;
494 
495     /* Switch device to Wi-Fi STA mode irrespective of
496      * whether provisioning was completed or not */
497     esp_wifi_set_mode(WIFI_MODE_STA);
498     ESP_LOGI(TAG, "Provisioning stopped");
499 
500     if (is_this_a_task) {
501         ACQUIRE_LOCK(prov_ctx_lock);
502         prov_ctx->prov_state = WIFI_PROV_STATE_IDLE;
503         RELEASE_LOCK(prov_ctx_lock);
504 
505         ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_END);
506         if (scheme_cb) {
507             scheme_cb(scheme_data, WIFI_PROV_END, NULL);
508         }
509         if (app_cb) {
510             app_cb(app_data, WIFI_PROV_END, NULL);
511         }
512         if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_END, NULL, 0, portMAX_DELAY) != ESP_OK) {
513             ESP_LOGE(TAG, "Failed to post event WIFI_PROV_END");
514         }
515 
516         vTaskDelete(NULL);
517     }
518 }
519 
520 /* This will do one of these:
521  * 1) if blocking is false, start a task for stopping the provisioning service (returns true)
522  * 2) if blocking is true, stop provisioning service immediately (returns true)
523  * 3) if service was already in the process of termination, in blocking mode this will
524  *    wait till the service is stopped (returns false)
525  * 4) if service was not running, this will return immediately (returns false)
526  *
527  * NOTE: This function should be called only after ensuring that the context
528  * is valid and the control mutex is locked
529  *
530  * NOTE: When blocking mode is selected, the event callbacks are not executed.
531  * This help with de-initialization.
532  */
wifi_prov_mgr_stop_service(bool blocking)533 static bool wifi_prov_mgr_stop_service(bool blocking)
534 {
535     if (blocking) {
536         /* Wait for any ongoing calls to wifi_prov_mgr_start_service() or
537          * wifi_prov_mgr_stop_service() from another thread to finish */
538         while (prov_ctx && (
539             prov_ctx->prov_state == WIFI_PROV_STATE_STARTING ||
540             prov_ctx->prov_state == WIFI_PROV_STATE_STOPPING)) {
541             RELEASE_LOCK(prov_ctx_lock);
542             vTaskDelay(100 / portTICK_PERIOD_MS);
543             ACQUIRE_LOCK(prov_ctx_lock);
544         }
545     } else {
546         /* Wait for any ongoing call to wifi_prov_mgr_start_service()
547          * from another thread to finish */
548         while (prov_ctx &&
549             prov_ctx->prov_state == WIFI_PROV_STATE_STARTING) {
550             RELEASE_LOCK(prov_ctx_lock);
551             vTaskDelay(100 / portTICK_PERIOD_MS);
552             ACQUIRE_LOCK(prov_ctx_lock);
553         }
554 
555         if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_STOPPING) {
556             ESP_LOGD(TAG, "Provisioning is already stopping");
557             return false;
558         }
559     }
560 
561     if (!prov_ctx || prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) {
562         ESP_LOGD(TAG, "Provisioning not running");
563         return false;
564     }
565 
566     /* Timers not needed anymore */
567     if (prov_ctx->autostop_timer) {
568         esp_timer_stop(prov_ctx->autostop_timer);
569         esp_timer_delete(prov_ctx->autostop_timer);
570         prov_ctx->autostop_timer = NULL;
571     }
572 
573     if (prov_ctx->wifi_connect_timer) {
574         esp_timer_stop(prov_ctx->wifi_connect_timer);
575         esp_timer_delete(prov_ctx->wifi_connect_timer);
576         prov_ctx->wifi_connect_timer = NULL;
577     }
578 
579     ESP_LOGD(TAG, "Stopping provisioning");
580     prov_ctx->prov_state = WIFI_PROV_STATE_STOPPING;
581 
582     /* Free proof of possession */
583     if (prov_ctx->pop.data) {
584         free((void *)prov_ctx->pop.data);
585         prov_ctx->pop.data = NULL;
586     }
587 
588     /* Delete all scan results */
589     for (uint16_t channel = 0; channel < 14; channel++) {
590         free(prov_ctx->ap_list[channel]);
591         prov_ctx->ap_list[channel] = NULL;
592     }
593     prov_ctx->scanning = false;
594     for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) {
595         prov_ctx->ap_list_sorted[i] = NULL;
596     }
597 
598     /* Remove event handler */
599     esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID,
600                                  wifi_prov_mgr_event_handler_internal);
601     esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP,
602                                  wifi_prov_mgr_event_handler_internal);
603 
604     if (blocking) {
605         /* Run the cleanup without launching a separate task. Also the
606          * WIFI_PROV_END event is not emitted in this case */
607         RELEASE_LOCK(prov_ctx_lock);
608         prov_stop_task((void *)0);
609         ACQUIRE_LOCK(prov_ctx_lock);
610         prov_ctx->prov_state = WIFI_PROV_STATE_IDLE;
611     } else {
612         /* Launch cleanup task to perform the cleanup asynchronously.
613          * It is important to do this asynchronously because, there are
614          * situations in which the transport level resources have to be
615          * released - some duration after - returning from a call to
616          * wifi_prov_mgr_stop_provisioning(), like when it is called
617          * inside a protocomm handler */
618         if (xTaskCreate(prov_stop_task, "prov_stop_task", 4096, (void *)1, tskIDLE_PRIORITY, NULL) != pdPASS) {
619             ESP_LOGE(TAG, "Failed to create prov_stop_task!");
620             abort();
621         }
622         ESP_LOGD(TAG, "Provisioning scheduled for stopping");
623     }
624     return true;
625 }
626 
627 /* Task spawned by timer callback */
stop_prov_timer_cb(void * arg)628 static void stop_prov_timer_cb(void *arg)
629 {
630     wifi_prov_mgr_stop_provisioning();
631 }
632 
wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay)633 esp_err_t wifi_prov_mgr_disable_auto_stop(uint32_t cleanup_delay)
634 {
635     if (!prov_ctx_lock) {
636         ESP_LOGE(TAG, "Provisioning manager not initialized");
637         return ESP_ERR_INVALID_STATE;
638     }
639 
640     esp_err_t ret = ESP_FAIL;
641     ACQUIRE_LOCK(prov_ctx_lock);
642 
643     if (prov_ctx && prov_ctx->prov_state == WIFI_PROV_STATE_IDLE) {
644         prov_ctx->mgr_info.capabilities.no_auto_stop = true;
645         prov_ctx->cleanup_delay = cleanup_delay;
646         ret = ESP_OK;
647     } else {
648         ret = ESP_ERR_INVALID_STATE;
649     }
650 
651     RELEASE_LOCK(prov_ctx_lock);
652     return ret;
653 }
654 
655 /* Call this if provisioning is completed before the timeout occurs */
wifi_prov_mgr_done(void)656 esp_err_t wifi_prov_mgr_done(void)
657 {
658     if (!prov_ctx_lock) {
659         ESP_LOGE(TAG, "Provisioning manager not initialized");
660         return ESP_ERR_INVALID_STATE;
661     }
662 
663     bool auto_stop_enabled = false;
664     ACQUIRE_LOCK(prov_ctx_lock);
665     if (prov_ctx && !prov_ctx->mgr_info.capabilities.no_auto_stop) {
666         auto_stop_enabled = true;
667     }
668     RELEASE_LOCK(prov_ctx_lock);
669 
670     /* Stop provisioning if auto stop is enabled */
671     if (auto_stop_enabled) {
672         wifi_prov_mgr_stop_provisioning();
673     }
674     return ESP_OK;
675 }
676 
update_wifi_scan_results(void)677 static esp_err_t update_wifi_scan_results(void)
678 {
679     if (!prov_ctx->scanning) {
680         return ESP_ERR_INVALID_STATE;
681     }
682     ESP_LOGD(TAG, "Scan finished");
683 
684     esp_err_t ret = ESP_FAIL;
685     uint16_t count = 0;
686     uint16_t curr_channel = prov_ctx->curr_channel;
687 
688     if (prov_ctx->ap_list[curr_channel]) {
689         free(prov_ctx->ap_list[curr_channel]);
690         prov_ctx->ap_list[curr_channel] = NULL;
691         prov_ctx->ap_list_len[curr_channel] = 0;
692     }
693 
694     if (esp_wifi_scan_get_ap_num(&count) != ESP_OK) {
695         ESP_LOGE(TAG, "Failed to get count of scanned APs");
696         goto exit;
697     }
698 
699     if (!count) {
700         ESP_LOGD(TAG, "Scan result empty");
701         ret = ESP_OK;
702         goto exit;
703     }
704 
705     prov_ctx->ap_list[curr_channel] = (wifi_ap_record_t *) calloc(count, sizeof(wifi_ap_record_t));
706     if (!prov_ctx->ap_list[curr_channel]) {
707         ESP_LOGE(TAG, "Failed to allocate memory for AP list");
708         goto exit;
709     }
710     if (esp_wifi_scan_get_ap_records(&count, prov_ctx->ap_list[curr_channel]) != ESP_OK) {
711         ESP_LOGE(TAG, "Failed to get scanned AP records");
712         goto exit;
713     }
714     prov_ctx->ap_list_len[curr_channel] = count;
715 
716     if (prov_ctx->channels_per_group) {
717         ESP_LOGD(TAG, "Scan results for channel %d :", curr_channel);
718     } else {
719         ESP_LOGD(TAG, "Scan results :");
720     }
721     ESP_LOGD(TAG, "\tS.N. %-32s %-12s %s %s", "SSID", "BSSID", "RSSI", "AUTH");
722     for (uint8_t i = 0; i < prov_ctx->ap_list_len[curr_channel]; i++) {
723         ESP_LOGD(TAG, "\t[%2d] %-32s %02x%02x%02x%02x%02x%02x %4d %4d", i,
724                  prov_ctx->ap_list[curr_channel][i].ssid,
725                  prov_ctx->ap_list[curr_channel][i].bssid[0],
726                  prov_ctx->ap_list[curr_channel][i].bssid[1],
727                  prov_ctx->ap_list[curr_channel][i].bssid[2],
728                  prov_ctx->ap_list[curr_channel][i].bssid[3],
729                  prov_ctx->ap_list[curr_channel][i].bssid[4],
730                  prov_ctx->ap_list[curr_channel][i].bssid[5],
731                  prov_ctx->ap_list[curr_channel][i].rssi,
732                  prov_ctx->ap_list[curr_channel][i].authmode);
733     }
734 
735     /* Store results in sorted list */
736     {
737         int rc = MIN(count, MAX_SCAN_RESULTS);
738         int is = MAX_SCAN_RESULTS - rc - 1;
739         while (rc > 0 && is >= 0) {
740             if (prov_ctx->ap_list_sorted[is]) {
741                 if (prov_ctx->ap_list_sorted[is]->rssi > prov_ctx->ap_list[curr_channel][rc - 1].rssi) {
742                     prov_ctx->ap_list_sorted[is + rc] = &prov_ctx->ap_list[curr_channel][rc - 1];
743                     rc--;
744                     continue;
745                 }
746                 prov_ctx->ap_list_sorted[is + rc] = prov_ctx->ap_list_sorted[is];
747             }
748             is--;
749         }
750         while (rc > 0) {
751             prov_ctx->ap_list_sorted[rc - 1] = &prov_ctx->ap_list[curr_channel][rc - 1];
752             rc--;
753         }
754     }
755 
756     ret = ESP_OK;
757     exit:
758 
759     if (!prov_ctx->channels_per_group) {
760         /* All channel scan was performed
761          * so nothing more to do */
762         prov_ctx->scanning = false;
763         goto final;
764     }
765 
766     curr_channel = prov_ctx->curr_channel = (prov_ctx->curr_channel + 1) % 14;
767     if (ret != ESP_OK || curr_channel == 0) {
768         prov_ctx->scanning = false;
769         goto final;
770     }
771 
772     if ((curr_channel % prov_ctx->channels_per_group) == 0) {
773         vTaskDelay(120 / portTICK_PERIOD_MS);
774     }
775 
776     ESP_LOGD(TAG, "Scan starting on channel %u...", curr_channel);
777     prov_ctx->scan_cfg.channel = curr_channel;
778     ret = esp_wifi_scan_start(&prov_ctx->scan_cfg, false);
779     if (ret != ESP_OK) {
780         ESP_LOGE(TAG, "Failed to start scan");
781         prov_ctx->scanning = false;
782         goto final;
783     }
784     ESP_LOGD(TAG, "Scan started");
785 
786     final:
787 
788     return ret;
789 }
790 
791 /* DEPRECATED : Event handler for starting/stopping provisioning.
792  * To be called from within the context of the main
793  * event handler */
wifi_prov_mgr_event_handler(void * ctx,system_event_t * event)794 esp_err_t wifi_prov_mgr_event_handler(void *ctx, system_event_t *event)
795 {
796     return ESP_OK;
797 }
798 
wifi_prov_mgr_event_handler_internal(void * arg,esp_event_base_t event_base,int32_t event_id,void * event_data)799 static void wifi_prov_mgr_event_handler_internal(
800     void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
801 {
802     if (!prov_ctx_lock) {
803         ESP_LOGE(TAG, "Provisioning manager not initialized");
804         return;
805     }
806     ACQUIRE_LOCK(prov_ctx_lock);
807 
808     /* If pointer to provisioning application data is NULL
809      * then provisioning manager is not running, therefore
810      * return with error to allow the global handler to act */
811     if (!prov_ctx) {
812         RELEASE_LOCK(prov_ctx_lock);
813         return;
814     }
815 
816     /* If scan completed then update scan result */
817     if (prov_ctx->prov_state == WIFI_PROV_STATE_STARTED &&
818         event_base == WIFI_EVENT &&
819         event_id == WIFI_EVENT_SCAN_DONE) {
820         update_wifi_scan_results();
821     }
822 
823     /* Only handle events when credential is received and
824      * Wi-Fi STA is yet to complete trying the connection */
825     if (prov_ctx->prov_state < WIFI_PROV_STATE_CRED_RECV) {
826         RELEASE_LOCK(prov_ctx_lock);
827         return;
828     }
829 
830     if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
831         ESP_LOGI(TAG, "STA Start");
832         /* Once configuration is received through protocomm,
833          * device is started as station. Once station starts,
834          * wait for connection to establish with configured
835          * host SSID and password */
836         prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING;
837     } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
838         ESP_LOGI(TAG, "STA Got IP");
839         /* Station got IP. That means configuration is successful. */
840         prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTED;
841         prov_ctx->prov_state = WIFI_PROV_STATE_SUCCESS;
842 
843         /* If auto stop is enabled (default), schedule timer to
844          * stop provisioning after configured timeout. */
845         if (!prov_ctx->mgr_info.capabilities.no_auto_stop) {
846             ESP_LOGD(TAG, "Starting %d sec timer for stop_prov_timer_cb()",
847                         CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT);
848             esp_timer_start_once(prov_ctx->autostop_timer, CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT * 1000000U);
849         }
850 
851         /* Execute user registered callback handler */
852         execute_event_cb(WIFI_PROV_CRED_SUCCESS, NULL, 0);
853     } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
854         ESP_LOGE(TAG, "STA Disconnected");
855         /* Station couldn't connect to configured host SSID */
856         prov_ctx->wifi_state = WIFI_PROV_STA_DISCONNECTED;
857 
858         wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data;
859         ESP_LOGE(TAG, "Disconnect reason : %d", disconnected->reason);
860 
861         /* Set code corresponding to the reason for disconnection */
862         switch (disconnected->reason) {
863         case WIFI_REASON_AUTH_EXPIRE:
864         case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
865         case WIFI_REASON_AUTH_FAIL:
866         case WIFI_REASON_ASSOC_EXPIRE:
867         case WIFI_REASON_HANDSHAKE_TIMEOUT:
868         case WIFI_REASON_MIC_FAILURE:
869             ESP_LOGE(TAG, "STA Auth Error");
870             prov_ctx->wifi_disconnect_reason = WIFI_PROV_STA_AUTH_ERROR;
871             break;
872         case WIFI_REASON_NO_AP_FOUND:
873             ESP_LOGE(TAG, "STA AP Not found");
874             prov_ctx->wifi_disconnect_reason = WIFI_PROV_STA_AP_NOT_FOUND;
875             break;
876         default:
877             /* If none of the expected reasons,
878              * retry connecting to host SSID */
879             prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING;
880             esp_wifi_connect();
881         }
882 
883         /* In case of disconnection, update state of service and
884          * run the event handler with disconnection reason as data */
885         if (prov_ctx->wifi_state == WIFI_PROV_STA_DISCONNECTED) {
886             prov_ctx->prov_state = WIFI_PROV_STATE_FAIL;
887             wifi_prov_sta_fail_reason_t reason = prov_ctx->wifi_disconnect_reason;
888             /* Execute user registered callback handler */
889             execute_event_cb(WIFI_PROV_CRED_FAIL, (void *)&reason, sizeof(reason));
890         }
891     }
892 
893     RELEASE_LOCK(prov_ctx_lock);
894 }
895 
wifi_prov_mgr_wifi_scan_start(bool blocking,bool passive,uint8_t group_channels,uint32_t period_ms)896 esp_err_t wifi_prov_mgr_wifi_scan_start(bool blocking, bool passive,
897                                         uint8_t group_channels, uint32_t period_ms)
898 {
899     if (!prov_ctx_lock) {
900         ESP_LOGE(TAG, "Provisioning manager not initialized");
901         return ESP_ERR_INVALID_STATE;
902     }
903     ACQUIRE_LOCK(prov_ctx_lock);
904 
905     if (!prov_ctx) {
906         ESP_LOGE(TAG, "Provisioning manager not initialized");
907         RELEASE_LOCK(prov_ctx_lock);
908         return ESP_ERR_INVALID_STATE;
909     }
910 
911     if (prov_ctx->scanning) {
912         ESP_LOGD(TAG, "Scan already running");
913         RELEASE_LOCK(prov_ctx_lock);
914         return ESP_OK;
915     }
916 
917     /* Clear sorted list for new entries */
918     for (uint8_t i = 0; i < MAX_SCAN_RESULTS; i++) {
919         prov_ctx->ap_list_sorted[i] = NULL;
920     }
921 
922     if (passive) {
923         prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_PASSIVE;
924 /* We do not recommend scan configuration modification in Wi-Fi and BT coexistence mode */
925 #if !CONFIG_BT_ENABLED
926         prov_ctx->scan_cfg.scan_time.passive = period_ms;
927 #endif
928     } else {
929         prov_ctx->scan_cfg.scan_type = WIFI_SCAN_TYPE_ACTIVE;
930 /* We do not recommend scan configuration modification in Wi-Fi and BT coexistence mode */
931 #if !CONFIG_BT_ENABLED
932         prov_ctx->scan_cfg.scan_time.active.min = period_ms;
933         prov_ctx->scan_cfg.scan_time.active.max = period_ms;
934 #endif
935     }
936     prov_ctx->channels_per_group = group_channels;
937 
938     if (prov_ctx->channels_per_group) {
939         ESP_LOGD(TAG, "Scan starting on channel 1...");
940         prov_ctx->scan_cfg.channel = 1;
941     } else {
942         ESP_LOGD(TAG, "Scan starting...");
943         prov_ctx->scan_cfg.channel = 0;
944     }
945 
946     if (esp_wifi_scan_start(&prov_ctx->scan_cfg, false) != ESP_OK) {
947         ESP_LOGE(TAG, "Failed to start scan");
948         RELEASE_LOCK(prov_ctx_lock);
949         return ESP_FAIL;
950     }
951 
952     ESP_LOGD(TAG, "Scan started");
953     prov_ctx->scanning = true;
954     prov_ctx->curr_channel = prov_ctx->scan_cfg.channel;
955     RELEASE_LOCK(prov_ctx_lock);
956 
957     /* If scan is to be non-blocking, return immediately */
958     if (!blocking) {
959         return ESP_OK;
960     }
961 
962     /* Loop till scan is complete */
963     bool scanning = true;
964     while (scanning) {
965         ACQUIRE_LOCK(prov_ctx_lock);
966         scanning = (prov_ctx && prov_ctx->scanning);
967         RELEASE_LOCK(prov_ctx_lock);
968 
969         /* 120ms delay is  sufficient for Wi-Fi beacons to be sent */
970         vTaskDelay(120 / portTICK_PERIOD_MS);
971     }
972     return ESP_OK;
973 }
974 
wifi_prov_mgr_wifi_scan_finished(void)975 bool wifi_prov_mgr_wifi_scan_finished(void)
976 {
977     bool scan_finished = true;
978     if (!prov_ctx_lock) {
979         ESP_LOGE(TAG, "Provisioning manager not initialized");
980         return scan_finished;
981     }
982 
983     ACQUIRE_LOCK(prov_ctx_lock);
984     if (!prov_ctx) {
985         ESP_LOGE(TAG, "Provisioning manager not initialized");
986         RELEASE_LOCK(prov_ctx_lock);
987         return scan_finished;
988     }
989 
990     scan_finished = !prov_ctx->scanning;
991     RELEASE_LOCK(prov_ctx_lock);
992     return scan_finished;
993 }
994 
wifi_prov_mgr_wifi_scan_result_count(void)995 uint16_t wifi_prov_mgr_wifi_scan_result_count(void)
996 {
997     uint16_t rval = 0;
998     if (!prov_ctx_lock) {
999         ESP_LOGE(TAG, "Provisioning manager not initialized");
1000         return rval;
1001     }
1002 
1003     ACQUIRE_LOCK(prov_ctx_lock);
1004     if (!prov_ctx) {
1005         ESP_LOGE(TAG, "Provisioning manager not initialized");
1006         RELEASE_LOCK(prov_ctx_lock);
1007         return rval;
1008     }
1009 
1010     while (rval < MAX_SCAN_RESULTS) {
1011         if (!prov_ctx->ap_list_sorted[rval]) {
1012             break;
1013         }
1014         rval++;
1015     }
1016     RELEASE_LOCK(prov_ctx_lock);
1017     return rval;
1018 }
1019 
wifi_prov_mgr_wifi_scan_result(uint16_t index)1020 const wifi_ap_record_t *wifi_prov_mgr_wifi_scan_result(uint16_t index)
1021 {
1022     const wifi_ap_record_t *rval = NULL;
1023     if (!prov_ctx_lock) {
1024         ESP_LOGE(TAG, "Provisioning manager not initialized");
1025         return rval;
1026     }
1027 
1028     ACQUIRE_LOCK(prov_ctx_lock);
1029     if (!prov_ctx) {
1030         ESP_LOGE(TAG, "Provisioning manager not initialized");
1031         RELEASE_LOCK(prov_ctx_lock);
1032         return rval;
1033     }
1034 
1035     if (index < MAX_SCAN_RESULTS) {
1036         rval = prov_ctx->ap_list_sorted[index];
1037     }
1038     RELEASE_LOCK(prov_ctx_lock);
1039     return rval;
1040 }
1041 
wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t * state)1042 esp_err_t wifi_prov_mgr_get_wifi_state(wifi_prov_sta_state_t *state)
1043 {
1044     if (!prov_ctx_lock) {
1045         ESP_LOGE(TAG, "Provisioning manager not initialized");
1046         return ESP_ERR_INVALID_STATE;
1047     }
1048 
1049     ACQUIRE_LOCK(prov_ctx_lock);
1050     if (prov_ctx == NULL || state == NULL) {
1051         RELEASE_LOCK(prov_ctx_lock);
1052         return ESP_FAIL;
1053     }
1054 
1055     *state = prov_ctx->wifi_state;
1056     RELEASE_LOCK(prov_ctx_lock);
1057     return ESP_OK;
1058 }
1059 
wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t * reason)1060 esp_err_t wifi_prov_mgr_get_wifi_disconnect_reason(wifi_prov_sta_fail_reason_t *reason)
1061 {
1062     if (!prov_ctx_lock) {
1063         ESP_LOGE(TAG, "Provisioning manager not initialized");
1064         return ESP_ERR_INVALID_STATE;
1065     }
1066 
1067     ACQUIRE_LOCK(prov_ctx_lock);
1068     if (prov_ctx == NULL || reason == NULL) {
1069         RELEASE_LOCK(prov_ctx_lock);
1070         return ESP_FAIL;
1071     }
1072 
1073     if (prov_ctx->wifi_state != WIFI_PROV_STA_DISCONNECTED) {
1074         RELEASE_LOCK(prov_ctx_lock);
1075         return ESP_FAIL;
1076     }
1077 
1078     *reason = prov_ctx->wifi_disconnect_reason;
1079     RELEASE_LOCK(prov_ctx_lock);
1080     return ESP_OK;
1081 }
1082 
debug_print_wifi_credentials(wifi_sta_config_t sta,const char * pretext)1083 static void debug_print_wifi_credentials(wifi_sta_config_t sta, const char* pretext)
1084 {
1085     size_t passlen = strlen((const char*) sta.password);
1086     ESP_LOGD(TAG, "%s Wi-Fi SSID     : %.*s", pretext,
1087              strnlen((const char *) sta.ssid, sizeof(sta.ssid)), (const char *) sta.ssid);
1088 
1089     if (passlen) {
1090         /* Mask password partially if longer than 3, else mask it completely */
1091         memset(sta.password + (passlen > 3), '*', passlen - 2*(passlen > 3));
1092         ESP_LOGD(TAG, "%s Wi-Fi Password : %s", pretext, (const char *) sta.password);
1093     }
1094 }
1095 
wifi_prov_mgr_is_provisioned(bool * provisioned)1096 esp_err_t wifi_prov_mgr_is_provisioned(bool *provisioned)
1097 {
1098     if (!provisioned) {
1099         return ESP_ERR_INVALID_ARG;
1100     }
1101 
1102     *provisioned = false;
1103 
1104     if (!prov_ctx_lock) {
1105         ESP_LOGE(TAG, "Provisioning manager not initialized");
1106         return ESP_ERR_INVALID_STATE;
1107     }
1108 
1109     /* Get Wi-Fi Station configuration */
1110     wifi_config_t wifi_cfg;
1111     if (esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg) != ESP_OK) {
1112         return ESP_FAIL;
1113     }
1114 
1115     if (strlen((const char *) wifi_cfg.sta.ssid)) {
1116         *provisioned = true;
1117         debug_print_wifi_credentials(wifi_cfg.sta, "Found");
1118     }
1119     return ESP_OK;
1120 }
1121 
wifi_connect_timer_cb(void * arg)1122 static void wifi_connect_timer_cb(void *arg)
1123 {
1124     if (esp_wifi_connect() != ESP_OK) {
1125         ESP_LOGE(TAG, "Failed to connect Wi-Fi");
1126     }
1127 }
1128 
wifi_prov_mgr_configure_sta(wifi_config_t * wifi_cfg)1129 esp_err_t wifi_prov_mgr_configure_sta(wifi_config_t *wifi_cfg)
1130 {
1131     if (!prov_ctx_lock) {
1132         ESP_LOGE(TAG, "Provisioning manager not initialized");
1133         return ESP_ERR_INVALID_STATE;
1134     }
1135 
1136     ACQUIRE_LOCK(prov_ctx_lock);
1137     if (!prov_ctx) {
1138         ESP_LOGE(TAG, "Invalid state of Provisioning app");
1139         RELEASE_LOCK(prov_ctx_lock);
1140         return ESP_FAIL;
1141     }
1142     if (prov_ctx->prov_state >= WIFI_PROV_STATE_CRED_RECV) {
1143         ESP_LOGE(TAG, "Wi-Fi credentials already received by provisioning app");
1144         RELEASE_LOCK(prov_ctx_lock);
1145         return ESP_FAIL;
1146     }
1147     debug_print_wifi_credentials(wifi_cfg->sta, "Received");
1148 
1149     /* Configure Wi-Fi as both AP and/or Station */
1150     if (esp_wifi_set_mode(prov_ctx->mgr_config.scheme.wifi_mode) != ESP_OK) {
1151         ESP_LOGE(TAG, "Failed to set Wi-Fi mode");
1152         RELEASE_LOCK(prov_ctx_lock);
1153         return ESP_FAIL;
1154     }
1155 
1156     /* Don't release mutex yet as it is possible that right after
1157      * esp_wifi_connect()  is called below, the related Wi-Fi event
1158      * happens even before manager state is updated in the next
1159      * few lines causing the internal event handler to miss */
1160 
1161     /* Set Wi-Fi storage again to flash to keep the newly
1162      * provided credentials on NVS */
1163     if (esp_wifi_set_storage(WIFI_STORAGE_FLASH) != ESP_OK) {
1164         ESP_LOGE(TAG, "Failed to set storage Wi-Fi");
1165         RELEASE_LOCK(prov_ctx_lock);
1166         return ESP_FAIL;
1167     }
1168     /* Configure Wi-Fi station with host credentials
1169      * provided during provisioning */
1170     if (esp_wifi_set_config(WIFI_IF_STA, wifi_cfg) != ESP_OK) {
1171         ESP_LOGE(TAG, "Failed to set Wi-Fi configuration");
1172         RELEASE_LOCK(prov_ctx_lock);
1173         return ESP_FAIL;
1174     }
1175     /* Connect to AP after one second so that the response can
1176      * be sent to the client successfully, before a channel change happens*/
1177     if (esp_timer_start_once(prov_ctx->wifi_connect_timer, 1000 * 1000U) != ESP_OK) {
1178         ESP_LOGE(TAG, "Failed to start Wi-Fi connect timer");
1179         RELEASE_LOCK(prov_ctx_lock);
1180         return ESP_FAIL;
1181     }
1182 
1183     /* Reset Wi-Fi station state for provisioning app */
1184     prov_ctx->wifi_state = WIFI_PROV_STA_CONNECTING;
1185     prov_ctx->prov_state = WIFI_PROV_STATE_CRED_RECV;
1186     /* Execute user registered callback handler */
1187     execute_event_cb(WIFI_PROV_CRED_RECV, (void *)&wifi_cfg->sta, sizeof(wifi_cfg->sta));
1188     RELEASE_LOCK(prov_ctx_lock);
1189 
1190     return ESP_OK;
1191 }
1192 
wifi_prov_mgr_init(wifi_prov_mgr_config_t config)1193 esp_err_t wifi_prov_mgr_init(wifi_prov_mgr_config_t config)
1194 {
1195     if (!prov_ctx_lock) {
1196        /* Create mutex if this is the first time init is being called.
1197         * This is created only once and never deleted because if some
1198         * other thread is trying to take this mutex while it is being
1199         * deleted from another thread then the reference may become
1200         * invalid and cause exception */
1201         prov_ctx_lock = xSemaphoreCreateMutex();
1202         if (!prov_ctx_lock) {
1203             ESP_LOGE(TAG, "Failed to create mutex");
1204             return ESP_ERR_NO_MEM;
1205         }
1206     }
1207 
1208     void *fn_ptrs[] = {
1209         config.scheme.prov_stop,
1210         config.scheme.prov_start,
1211         config.scheme.new_config,
1212         config.scheme.delete_config,
1213         config.scheme.set_config_service,
1214         config.scheme.set_config_endpoint
1215     };
1216 
1217     /* All function pointers in the scheme structure must be non-null */
1218     for (size_t i = 0; i < sizeof(fn_ptrs)/sizeof(fn_ptrs[0]); i++) {
1219         if (!fn_ptrs[i]) {
1220             return ESP_ERR_INVALID_ARG;
1221         }
1222     }
1223 
1224     ACQUIRE_LOCK(prov_ctx_lock);
1225     if (prov_ctx) {
1226         ESP_LOGE(TAG, "Provisioning manager already initialized");
1227         RELEASE_LOCK(prov_ctx_lock);
1228         return ESP_ERR_INVALID_STATE;
1229     }
1230 
1231     /* Allocate memory for provisioning app data */
1232     prov_ctx = (struct wifi_prov_mgr_ctx *) calloc(1, sizeof(struct wifi_prov_mgr_ctx));
1233     if (!prov_ctx) {
1234         ESP_LOGE(TAG, "Error allocating memory for singleton instance");
1235         RELEASE_LOCK(prov_ctx_lock);
1236         return ESP_ERR_NO_MEM;
1237     }
1238 
1239     prov_ctx->mgr_config = config;
1240     prov_ctx->prov_state = WIFI_PROV_STATE_IDLE;
1241     prov_ctx->mgr_info.version = WIFI_PROV_MGR_VERSION;
1242 
1243     /* Allocate memory for provisioning scheme configuration */
1244     const wifi_prov_scheme_t *scheme = &prov_ctx->mgr_config.scheme;
1245     esp_err_t ret = ESP_OK;
1246     prov_ctx->prov_scheme_config = scheme->new_config();
1247     if (!prov_ctx->prov_scheme_config) {
1248         ESP_LOGE(TAG, "failed to allocate provisioning scheme configuration");
1249         ret = ESP_ERR_NO_MEM;
1250         goto exit;
1251     }
1252 
1253     ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-scan", 0xFF50);
1254     if (ret != ESP_OK) {
1255         ESP_LOGE(TAG, "failed to configure Wi-Fi scanning endpoint");
1256         goto exit;
1257     }
1258 
1259     ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-session", 0xFF51);
1260     if (ret != ESP_OK) {
1261         ESP_LOGE(TAG, "failed to configure security endpoint");
1262         goto exit;
1263     }
1264 
1265     ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "prov-config", 0xFF52);
1266     if (ret != ESP_OK) {
1267         ESP_LOGE(TAG, "failed to configure Wi-Fi configuration endpoint");
1268         goto exit;
1269     }
1270 
1271     ret = scheme->set_config_endpoint(prov_ctx->prov_scheme_config, "proto-ver", 0xFF53);
1272     if (ret != ESP_OK) {
1273         ESP_LOGE(TAG, "failed to configure version endpoint");
1274         goto exit;
1275     }
1276 
1277     /* Application specific custom endpoints will be assigned
1278      * incremental UUIDs starting after this value */
1279     prov_ctx->endpoint_uuid_used = 0xFF53;
1280 
1281     /* This delay is so that the client side app is notified first
1282      * and then the provisioning is stopped. Default is 1000ms. */
1283     prov_ctx->cleanup_delay = 1000;
1284 
1285 exit:
1286     if (ret != ESP_OK) {
1287         if (prov_ctx->prov_scheme_config) {
1288             config.scheme.delete_config(prov_ctx->prov_scheme_config);
1289         }
1290         free(prov_ctx);
1291     } else {
1292         execute_event_cb(WIFI_PROV_INIT, NULL, 0);
1293     }
1294     RELEASE_LOCK(prov_ctx_lock);
1295     return ret;
1296 }
1297 
wifi_prov_mgr_wait(void)1298 void wifi_prov_mgr_wait(void)
1299 {
1300     if (!prov_ctx_lock) {
1301         ESP_LOGE(TAG, "Provisioning manager not initialized");
1302         return;
1303     }
1304 
1305     while (1) {
1306         ACQUIRE_LOCK(prov_ctx_lock);
1307         if (prov_ctx &&
1308             prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) {
1309             RELEASE_LOCK(prov_ctx_lock);
1310             vTaskDelay(1000 / portTICK_PERIOD_MS);
1311             continue;
1312         }
1313         break;
1314     }
1315     RELEASE_LOCK(prov_ctx_lock);
1316 }
1317 
wifi_prov_mgr_deinit(void)1318 void wifi_prov_mgr_deinit(void)
1319 {
1320     if (!prov_ctx_lock) {
1321         ESP_LOGE(TAG, "Provisioning manager not initialized");
1322         return;
1323     }
1324 
1325     ACQUIRE_LOCK(prov_ctx_lock);
1326 
1327     /* This will do one of these:
1328      * 1) if found running, stop the provisioning service (returns true)
1329      * 2) if service was already in the process of termination, this will
1330      *    wait till the service is stopped (returns false)
1331      * 3) if service was not running, this will return immediately (returns false)
1332      */
1333     bool service_was_running = wifi_prov_mgr_stop_service(1);
1334 
1335      /* If service was not running, its also possible that manager
1336       * was not even initialized */
1337     if (!service_was_running && !prov_ctx) {
1338         ESP_LOGD(TAG, "Manager already de-initialized");
1339         RELEASE_LOCK(prov_ctx_lock);
1340         return;
1341     }
1342 
1343     if (prov_ctx->app_info_json) {
1344         cJSON_Delete(prov_ctx->app_info_json);
1345     }
1346 
1347     if (prov_ctx->prov_scheme_config) {
1348         prov_ctx->mgr_config.scheme.delete_config(prov_ctx->prov_scheme_config);
1349     }
1350 
1351     /* Extract the callbacks to be called post deinit */
1352     wifi_prov_cb_func_t app_cb = prov_ctx->mgr_config.app_event_handler.event_cb;
1353     void *app_data = prov_ctx->mgr_config.app_event_handler.user_data;
1354 
1355     wifi_prov_cb_func_t scheme_cb = prov_ctx->mgr_config.scheme_event_handler.event_cb;
1356     void *scheme_data = prov_ctx->mgr_config.scheme_event_handler.user_data;
1357 
1358     /* Free manager context */
1359     free(prov_ctx);
1360     prov_ctx = NULL;
1361     RELEASE_LOCK(prov_ctx_lock);
1362 
1363     /* If a running service was also stopped during de-initialization
1364      * then WIFI_PROV_END event also needs to be emitted before deinit */
1365     if (service_was_running) {
1366         ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_END);
1367         if (scheme_cb) {
1368             scheme_cb(scheme_data, WIFI_PROV_END, NULL);
1369         }
1370         if (app_cb) {
1371             app_cb(app_data, WIFI_PROV_END, NULL);
1372         }
1373         if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_END, NULL, 0, portMAX_DELAY) != ESP_OK) {
1374             ESP_LOGE(TAG, "Failed to post event WIFI_PROV_END");
1375         }
1376     }
1377 
1378     ESP_LOGD(TAG, "execute_event_cb : %d", WIFI_PROV_DEINIT);
1379 
1380     /* Execute deinit event callbacks */
1381     if (scheme_cb) {
1382         scheme_cb(scheme_data, WIFI_PROV_DEINIT, NULL);
1383     }
1384     if (app_cb) {
1385         app_cb(app_data, WIFI_PROV_DEINIT, NULL);
1386     }
1387     if (esp_event_post(WIFI_PROV_EVENT, WIFI_PROV_DEINIT, NULL, 0, portMAX_DELAY) != ESP_OK) {
1388         ESP_LOGE(TAG, "Failed to post event WIFI_PROV_DEINIT");
1389     }
1390 }
1391 
wifi_prov_mgr_start_provisioning(wifi_prov_security_t security,const char * pop,const char * service_name,const char * service_key)1392 esp_err_t wifi_prov_mgr_start_provisioning(wifi_prov_security_t security, const char *pop,
1393                                            const char *service_name, const char *service_key)
1394 {
1395     uint8_t restore_wifi_flag = 0;
1396 
1397     if (!prov_ctx_lock) {
1398         ESP_LOGE(TAG, "Provisioning manager not initialized");
1399         return ESP_ERR_INVALID_STATE;
1400     }
1401 
1402     ACQUIRE_LOCK(prov_ctx_lock);
1403     if (!prov_ctx) {
1404         ESP_LOGE(TAG, "Provisioning manager not initialized");
1405         RELEASE_LOCK(prov_ctx_lock);
1406         return ESP_ERR_INVALID_STATE;
1407     }
1408 
1409     if (prov_ctx->prov_state != WIFI_PROV_STATE_IDLE) {
1410         ESP_LOGE(TAG, "Provisioning service already started");
1411         RELEASE_LOCK(prov_ctx_lock);
1412         return ESP_ERR_INVALID_STATE;
1413     }
1414 
1415     esp_err_t ret = ESP_OK;
1416     /* Update state so that parallel call to wifi_prov_mgr_start_provisioning()
1417      * or wifi_prov_mgr_stop_provisioning() or wifi_prov_mgr_deinit() from another
1418      * thread doesn't interfere with this process */
1419     prov_ctx->prov_state = WIFI_PROV_STATE_STARTING;
1420 
1421     /* Start Wi-Fi in Station Mode.
1422      * This is necessary for scanning to work */
1423     ret = esp_wifi_set_mode(WIFI_MODE_STA);
1424     if (ret != ESP_OK) {
1425         ESP_LOGE(TAG, "Failed to set Wi-Fi mode to STA");
1426         goto err;
1427     }
1428     ret = esp_wifi_start();
1429     if (ret != ESP_OK) {
1430         ESP_LOGE(TAG, "Failed to start Wi-Fi");
1431         goto err;
1432     }
1433 
1434     /* Change Wi-Fi storage to RAM temporarily and erase any old
1435      * credentials in RAM(i.e. without erasing the copy on NVS). Also
1436      * call disconnect to make sure device doesn't remain connected
1437      * to the AP whose credentials were present earlier */
1438     wifi_config_t wifi_cfg_empty, wifi_cfg_old;
1439     memset(&wifi_cfg_empty, 0, sizeof(wifi_config_t));
1440     esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg_old);
1441     ret = esp_wifi_set_storage(WIFI_STORAGE_RAM);
1442     if (ret != ESP_OK) {
1443         ESP_LOGE(TAG, "Failed to set Wi-Fi storage to RAM");
1444         goto err;
1445     }
1446 
1447     /* WiFi storage needs to be restored before exiting this API */
1448     restore_wifi_flag |= WIFI_PROV_STORAGE_BIT;
1449     /* Erase Wi-Fi credentials in RAM, when call disconnect and user code
1450      * receive WIFI_EVENT_STA_DISCONNECTED and maybe call esp_wifi_connect, at
1451      * this time Wi-Fi will have no configuration to connect */
1452     ret = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_empty);
1453     if (ret != ESP_OK) {
1454         ESP_LOGE(TAG, "Failed to set empty Wi-Fi credentials");
1455         goto err;
1456     }
1457     /* WiFi settings needs to be restored if provisioning error before exiting this API */
1458     restore_wifi_flag |= WIFI_PROV_SETTING_BIT;
1459 
1460     ret = esp_wifi_disconnect();
1461     if (ret != ESP_OK) {
1462         ESP_LOGE(TAG, "Failed to disconnect");
1463         goto err;
1464     }
1465 
1466     /* Initialize app data */
1467     if (security == WIFI_PROV_SECURITY_0) {
1468         prov_ctx->mgr_info.capabilities.no_sec = true;
1469     } else if (pop) {
1470         prov_ctx->pop.len = strlen(pop);
1471         prov_ctx->pop.data = malloc(prov_ctx->pop.len);
1472         if (!prov_ctx->pop.data) {
1473             ESP_LOGE(TAG, "Unable to allocate PoP data");
1474             ret = ESP_ERR_NO_MEM;
1475             goto err;
1476         }
1477         memcpy((void *)prov_ctx->pop.data, pop, prov_ctx->pop.len);
1478     } else {
1479         prov_ctx->mgr_info.capabilities.no_pop = true;
1480     }
1481     prov_ctx->security = security;
1482 
1483 
1484     esp_timer_create_args_t wifi_connect_timer_conf = {
1485         .callback = wifi_connect_timer_cb,
1486         .arg = NULL,
1487         .dispatch_method = ESP_TIMER_TASK,
1488         .name = "wifi_prov_connect_tm"
1489     };
1490     ret = esp_timer_create(&wifi_connect_timer_conf, &prov_ctx->wifi_connect_timer);
1491     if (ret != ESP_OK) {
1492         ESP_LOGE(TAG, "Failed to create Wi-Fi connect timer");
1493         free((void *)prov_ctx->pop.data);
1494         goto err;
1495     }
1496 
1497     /* If auto stop on completion is enabled (default) create the stopping timer */
1498     if (!prov_ctx->mgr_info.capabilities.no_auto_stop) {
1499         /* Create timer object as a member of app data */
1500         esp_timer_create_args_t autostop_timer_conf = {
1501             .callback = stop_prov_timer_cb,
1502             .arg = NULL,
1503             .dispatch_method = ESP_TIMER_TASK,
1504             .name = "wifi_prov_autostop_tm"
1505         };
1506         ret = esp_timer_create(&autostop_timer_conf, &prov_ctx->autostop_timer);
1507         if (ret != ESP_OK) {
1508             ESP_LOGE(TAG, "Failed to create auto-stop timer");
1509             esp_timer_delete(prov_ctx->wifi_connect_timer);
1510             free((void *)prov_ctx->pop.data);
1511             goto err;
1512         }
1513     }
1514 
1515     /* System APIs for BLE / Wi-Fi will be called inside wifi_prov_mgr_start_service(),
1516      * which may trigger system level events. Hence, releasing the context lock will
1517      * ensure that wifi_prov_mgr_event_handler() doesn't block the global event_loop
1518      * handler when system events need to be handled */
1519     RELEASE_LOCK(prov_ctx_lock);
1520 
1521     /* Start provisioning service */
1522     ret = wifi_prov_mgr_start_service(service_name, service_key);
1523     if (ret != ESP_OK) {
1524         esp_timer_delete(prov_ctx->autostop_timer);
1525         esp_timer_delete(prov_ctx->wifi_connect_timer);
1526         free((void *)prov_ctx->pop.data);
1527     }
1528     ACQUIRE_LOCK(prov_ctx_lock);
1529     if (ret == ESP_OK) {
1530         prov_ctx->prov_state = WIFI_PROV_STATE_STARTED;
1531         /* Execute user registered callback handler */
1532         execute_event_cb(WIFI_PROV_START, NULL, 0);
1533         goto exit;
1534     }
1535 
1536 err:
1537     prov_ctx->prov_state = WIFI_PROV_STATE_IDLE;
1538     if (restore_wifi_flag & WIFI_PROV_SETTING_BIT) {
1539         /* Restore current WiFi settings, since provisioning start has failed */
1540         esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg_old);
1541     }
1542 exit:
1543     if (restore_wifi_flag & WIFI_PROV_STORAGE_BIT) {
1544         /* Restore WiFi storage back to FLASH */
1545         esp_wifi_set_storage(WIFI_STORAGE_FLASH);
1546     }
1547     RELEASE_LOCK(prov_ctx_lock);
1548     return ret;
1549 }
1550 
wifi_prov_mgr_stop_provisioning(void)1551 void wifi_prov_mgr_stop_provisioning(void)
1552 {
1553     if (!prov_ctx_lock) {
1554         ESP_LOGE(TAG, "Provisioning manager not initialized");
1555         return;
1556     }
1557 
1558     ACQUIRE_LOCK(prov_ctx_lock);
1559 
1560     /* Launches task for stopping the provisioning service. This will do one of these:
1561      * 1) start a task for stopping the provisioning service (returns true)
1562      * 2) if service was already in the process of termination, this will
1563      *    wait till the service is stopped (returns false)
1564      * 3) if service was not running, this will return immediately (returns false)
1565      */
1566     wifi_prov_mgr_stop_service(0);
1567 
1568     RELEASE_LOCK(prov_ctx_lock);
1569 }
1570 
wifi_prov_mgr_reset_provisioning(void)1571 esp_err_t wifi_prov_mgr_reset_provisioning(void)
1572 {
1573     esp_err_t ret = esp_wifi_restore();
1574 
1575     if (ret != ESP_OK) {
1576         ESP_LOGE(TAG, "esp_wifi_restore fail, ret is %d", ret);
1577         ret = ESP_FAIL;
1578     }
1579 
1580     return ret;
1581 }
1582 
wifi_prov_mgr_reset_sm_state_on_failure(void)1583 esp_err_t wifi_prov_mgr_reset_sm_state_on_failure(void)
1584 {
1585     if (!prov_ctx_lock) {
1586         ESP_LOGE(TAG, "Provisioning manager not initialized");
1587         return ESP_ERR_INVALID_STATE;
1588     }
1589 
1590     ACQUIRE_LOCK(prov_ctx_lock);
1591 
1592     esp_err_t err = ESP_OK;
1593     if (prov_ctx->prov_state != WIFI_PROV_STATE_FAIL) {
1594         ESP_LOGE(TAG, "Trying reset when not in failure state. Current state: %d", prov_ctx->prov_state);
1595         err = ESP_ERR_INVALID_STATE;
1596         goto exit;
1597     }
1598 
1599     wifi_config_t wifi_cfg = {0};
1600 
1601     err = esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
1602     if (err != ESP_OK) {
1603         ESP_LOGE(TAG, "Failed to set wifi config, 0x%x", err);
1604         goto exit;
1605     }
1606 
1607     prov_ctx->prov_state = WIFI_PROV_STATE_STARTED;
1608 
1609 exit:
1610     RELEASE_LOCK(prov_ctx_lock);
1611     return err;
1612 }
1613