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