1 /*
2  * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include "sdkconfig.h"
7 
8 #include <string.h>
9 #include <esp_log.h>
10 #include <esp_err.h>
11 #include <esp_wifi.h>
12 
13 #include <mdns.h>
14 #include <protocomm.h>
15 #include <protocomm_httpd.h>
16 
17 #include "wifi_provisioning/scheme_softap.h"
18 #include "wifi_provisioning_priv.h"
19 
20 typedef struct softap_config {
21     protocomm_httpd_config_t httpd_config;
22     char ssid[33];
23     char password[65];
24 } wifi_prov_softap_config_t;
25 
26 static const char *TAG = "wifi_prov_scheme_softap";
27 
28 extern const wifi_prov_scheme_t wifi_prov_scheme_softap;
29 static void *scheme_softap_prov_httpd_handle;
start_wifi_ap(const char * ssid,const char * pass)30 static esp_err_t start_wifi_ap(const char *ssid, const char *pass)
31 {
32     /* Build Wi-Fi configuration for AP mode */
33     wifi_config_t wifi_config = {
34         .ap = {
35             .max_connection = 5,
36         },
37     };
38 
39     /* SSID can be a non NULL terminated string if `ap.ssid_len` is specified
40      * Hence, memcpy is used to support 32 bytes long SSID per 802.11 standard */
41     const size_t ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid));
42     memcpy(wifi_config.ap.ssid, ssid, ssid_len);
43     wifi_config.ap.ssid_len = ssid_len;
44 
45     if (strlen(pass) == 0) {
46         memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password));
47         wifi_config.ap.authmode = WIFI_AUTH_OPEN;
48     } else {
49         strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
50         wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
51     }
52 
53     /* Run Wi-Fi in AP + STA mode with configuration built above */
54     esp_err_t err = esp_wifi_set_mode(WIFI_MODE_APSTA);
55     if (err != ESP_OK) {
56         ESP_LOGE(TAG, "Failed to set Wi-Fi mode : %d", err);
57         return err;
58     }
59     err = esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
60     if (err != ESP_OK) {
61         ESP_LOGE(TAG, "Failed to set Wi-Fi config : %d", err);
62         return err;
63     }
64 
65     return ESP_OK;
66 }
67 
prov_start(protocomm_t * pc,void * config)68 static esp_err_t prov_start(protocomm_t *pc, void *config)
69 {
70     if (!pc) {
71         ESP_LOGE(TAG, "Protocomm handle cannot be null");
72         return ESP_ERR_INVALID_ARG;
73     }
74 
75     if (!config) {
76         ESP_LOGE(TAG, "Cannot start with null configuration");
77         return ESP_ERR_INVALID_ARG;
78     }
79 
80     wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config;
81 
82     protocomm_httpd_config_t *httpd_config = &softap_config->httpd_config;
83 
84     if (scheme_softap_prov_httpd_handle) {
85         httpd_config->ext_handle_provided = true;
86         httpd_config->data.handle = scheme_softap_prov_httpd_handle;
87     }
88 
89     /* Start protocomm server on top of HTTP */
90     esp_err_t err = protocomm_httpd_start(pc, httpd_config);
91     if (err != ESP_OK) {
92         ESP_LOGE(TAG, "Failed to start protocomm HTTP server");
93         return err;
94     }
95 
96     /* Start Wi-Fi softAP with specified ssid and password */
97     err = start_wifi_ap(softap_config->ssid, softap_config->password);
98     if (err != ESP_OK) {
99         ESP_LOGE(TAG, "Failed to start Wi-Fi AP");
100         protocomm_httpd_stop(pc);
101         return err;
102     }
103 
104     /* Add mDNS service for allowing discovery of provisioning
105      * service on the SoftAP network (Optional). Even though
106      * this is an http service we identify it by _esp_wifi_prov so
107      * that application is free to use _http without conflict */
108     err = mdns_service_add("Wi-Fi Provisioning Service", "_esp_wifi_prov", "_tcp",
109                            softap_config->httpd_config.data.config.port, NULL, 0);
110     if (err != ESP_OK) {
111         /* mDNS is not mandatory for provisioning to work,
112          * so print warning and return without failure */
113         ESP_LOGW(TAG, "Error adding mDNS service! Check if mDNS is running");
114     } else {
115         /* Information to identify the roles of the various
116          * protocomm endpoint URIs provided by the service */
117         err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "version_endpoint", "/proto-ver");
118         err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "session_endpoint", "/prov-session");
119         err |= mdns_service_txt_item_set("_esp_wifi_prov", "_tcp", "config_endpoint", "/prov-config");
120         if (err != ESP_OK) {
121             ESP_LOGE(TAG, "Error adding mDNS service text item");
122         }
123     }
124     return ESP_OK;
125 }
126 
prov_stop(protocomm_t * pc)127 static esp_err_t prov_stop(protocomm_t *pc)
128 {
129     esp_err_t err = protocomm_httpd_stop(pc);
130     if (err != ESP_OK) {
131         ESP_LOGW(TAG, "Error occurred while stopping protocomm_httpd");
132     }
133 
134     mdns_service_remove("_esp_wifi_prov", "_tcp");
135     return err;
136 }
137 
new_config(void)138 static void *new_config(void)
139 {
140     wifi_prov_softap_config_t *softap_config = calloc(1, sizeof(wifi_prov_softap_config_t));
141     if (!softap_config) {
142         ESP_LOGE(TAG, "Error allocating memory for new configuration");
143         return NULL;
144     }
145     protocomm_httpd_config_t default_config = {
146         .data = {
147             .config = PROTOCOMM_HTTPD_DEFAULT_CONFIG()
148         }
149     };
150     softap_config->httpd_config = default_config;
151     return softap_config;
152 }
153 
delete_config(void * config)154 static void delete_config(void *config)
155 {
156     if (!config) {
157         ESP_LOGE(TAG, "Cannot delete null configuration");
158         return;
159     }
160 
161     wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config;
162     free(softap_config);
163 }
164 
set_config_service(void * config,const char * service_name,const char * service_key)165 static esp_err_t set_config_service(void *config, const char *service_name, const char *service_key)
166 {
167     if (!config) {
168         ESP_LOGE(TAG, "Cannot set null configuration");
169         return ESP_ERR_INVALID_ARG;
170     }
171 
172     if (!service_name) {
173         ESP_LOGE(TAG, "Service name cannot be NULL");
174         return ESP_ERR_INVALID_ARG;
175     }
176 
177     wifi_prov_softap_config_t *softap_config = (wifi_prov_softap_config_t *) config;
178     if (service_key) {
179         const int service_key_len = strlen(service_key);
180         if (service_key_len < 8 || service_key_len >= sizeof(softap_config->password)) {
181             ESP_LOGE(TAG, "Incorrect passphrase length for softAP: %d (Expected: Min - 8, Max - 64)", service_key_len);
182             return ESP_ERR_INVALID_ARG;
183         }
184         strlcpy(softap_config->password, service_key,  sizeof(softap_config->password));
185     }
186     strlcpy(softap_config->ssid, service_name, sizeof(softap_config->ssid));
187     return ESP_OK;
188 }
189 
set_config_endpoint(void * config,const char * endpoint_name,uint16_t uuid)190 static esp_err_t set_config_endpoint(void *config, const char *endpoint_name, uint16_t uuid)
191 {
192     return ESP_OK;
193 }
194 
wifi_prov_scheme_softap_set_httpd_handle(void * handle)195 void wifi_prov_scheme_softap_set_httpd_handle(void *handle)
196 {
197     scheme_softap_prov_httpd_handle = handle;
198 }
199 
200 const wifi_prov_scheme_t wifi_prov_scheme_softap = {
201     .prov_start          = prov_start,
202     .prov_stop           = prov_stop,
203     .new_config          = new_config,
204     .delete_config       = delete_config,
205     .set_config_service  = set_config_service,
206     .set_config_endpoint = set_config_endpoint,
207     .wifi_mode           = WIFI_MODE_APSTA
208 };
209