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 = ESP_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 = ESP_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