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