Merge branch 'bugfix/prov_examples_strlcpy_backport_v3.3' into 'release/v3.3'

Various bugfixes in Protocomm / Unified Provisioning (backport v3.3)

See merge request espressif/esp-idf!5369
This commit is contained in:
Angus Gratton 2019-07-08 14:04:49 +08:00
commit 2c9acce0ff
23 changed files with 327 additions and 142 deletions

View File

@ -198,7 +198,7 @@ static esp_err_t handle_session_command0(uint32_t session_id,
mbedtls_ecdh_context *ctx_server = malloc(sizeof(mbedtls_ecdh_context));
mbedtls_entropy_context *entropy = malloc(sizeof(mbedtls_entropy_context));
mbedtls_ctr_drbg_context *ctr_drbg = malloc(sizeof(mbedtls_ctr_drbg_context));
if (!ctx_server || !ctx_server || !ctr_drbg) {
if (!ctx_server || !entropy || !ctr_drbg) {
ESP_LOGE(TAG, "Failed to allocate memory for mbedtls context");
free(ctx_server);
free(entropy);

View File

@ -140,40 +140,56 @@ static void transport_simple_ble_read(esp_gatts_cb_event_t event, esp_gatt_if_t
static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param)
{
ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d",
param->write.handle, param->write.len);
ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d, offset = %d",
param->write.handle, param->write.len, param->write.offset);
esp_gatt_status_t status = ESP_GATT_OK;
if (prepare_write_env.prepare_buf == NULL) {
prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
if (prepare_write_env.prepare_buf == NULL) {
ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__);
status = ESP_GATT_NO_RESOURCES;
}
/* prepare_write_env.prepare_len = 0; */
/* Ensure that write data is not larger than max attribute length */
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
} else {
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
/* If prepare buffer is not allocated, then allocate it */
if (prepare_write_env.prepare_buf == NULL) {
prepare_write_env.prepare_len = 0;
prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
if (prepare_write_env.prepare_buf == NULL) {
ESP_LOGE(TAG, "%s , failed to allocate prepare buf", __func__);
status = ESP_GATT_NO_RESOURCES;
}
}
}
memcpy(prepare_write_env.prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env.prepare_len += param->write.len;
prepare_write_env.handle = param->write.handle;
/* If prepare buffer is allocated copy incoming data into it */
if (status == ESP_GATT_OK) {
memcpy(prepare_write_env.prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env.prepare_len += param->write.len;
prepare_write_env.handle = param->write.handle;
}
/* Send write response if needed */
if (param->write.need_rsp) {
esp_gatt_rsp_t gatt_rsp = {0};
gatt_rsp.attr_value.len = param->write.len;
gatt_rsp.attr_value.handle = param->write.handle;
gatt_rsp.attr_value.offset = param->write.offset;
gatt_rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
if (gatt_rsp.attr_value.len && param->write.value) {
memcpy(gatt_rsp.attr_value.value, param->write.value, param->write.len);
esp_err_t response_err;
/* If data was successfully appended to prepare buffer
* only then have it reflected in the response */
if (status == ESP_GATT_OK) {
esp_gatt_rsp_t gatt_rsp = {0};
gatt_rsp.attr_value.len = param->write.len;
gatt_rsp.attr_value.handle = param->write.handle;
gatt_rsp.attr_value.offset = param->write.offset;
gatt_rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
if (gatt_rsp.attr_value.len && param->write.value) {
memcpy(gatt_rsp.attr_value.value, param->write.value, param->write.len);
}
response_err = esp_ble_gatts_send_response(gatts_if,
param->write.conn_id, param->write.trans_id, status, &gatt_rsp);
} else {
response_err = esp_ble_gatts_send_response(gatts_if,
param->write.conn_id, param->write.trans_id, status, NULL);
}
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status,
&gatt_rsp);
if (response_err != ESP_OK) {
ESP_LOGE(TAG, "Send response error in prep write");
}
@ -195,7 +211,7 @@ static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t
ssize_t outlen = 0;
esp_err_t ret;
ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d",
ESP_LOGD(TAG, "Inside write with session - %d on attr handle = %d \nlen = %d, is_prep = %d",
param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep);
if (param->write.is_prep) {

View File

@ -50,9 +50,7 @@ static esp_err_t common_post_handler(httpd_req_t *req)
if (pc_httpd->sec && pc_httpd->sec->close_transport_session) {
ret = pc_httpd->sec->close_transport_session(session_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to close session with ID: %d", session_id);
ret = ESP_FAIL;
goto out;
ESP_LOGW(TAG, "Error closing session with ID: %d", session_id);
}
}
session_id = PROTOCOMM_NO_SESSION_ID;
@ -241,6 +239,7 @@ esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t
pc->add_endpoint = protocomm_httpd_add_endpoint;
pc->remove_endpoint = protocomm_httpd_remove_endpoint;
pc_httpd = pc;
session_id = PROTOCOMM_NO_SESSION_ID;
return ESP_OK;
}
@ -256,6 +255,7 @@ esp_err_t protocomm_httpd_stop(protocomm_t *pc)
}
pc_httpd->priv = NULL;
pc_httpd = NULL;
session_id = PROTOCOMM_NO_SESSION_ID;
return ESP_OK;
}
return ESP_ERR_INVALID_ARG;

View File

@ -76,7 +76,7 @@ typedef struct {
*/
typedef struct {
char ssid[33]; /*!< SSID of the AP to which the slave is to be connected */
char password[65]; /*!< Password of the AP */
char password[64]; /*!< Password of the AP */
char bssid[6]; /*!< BSSID of the AP */
uint8_t channel; /*!< Channel of the AP */
} wifi_prov_config_set_data_t;

View File

@ -151,15 +151,36 @@ static esp_err_t cmd_set_config_handler(WiFiConfigPayload *req,
wifi_prov_config_set_data_t req_data;
memset(&req_data, 0, sizeof(req_data));
memcpy(req_data.ssid, req->cmd_set_config->ssid.data,
req->cmd_set_config->ssid.len);
memcpy(req_data.password, req->cmd_set_config->passphrase.data,
req->cmd_set_config->passphrase.len);
memcpy(req_data.bssid, req->cmd_set_config->bssid.data,
req->cmd_set_config->bssid.len);
req_data.channel = req->cmd_set_config->channel;
if (h->set_config_handler(&req_data, &h->ctx) == ESP_OK) {
resp_payload->status = STATUS__Success;
/* Check arguments provided in protobuf packet:
* - SSID / Passphrase string length must be within the standard limits
* - BSSID must either be NULL or have length equal to that imposed by the standard
* If any of these conditions are not satisfied, don't invoke the handler and
* send error status without closing connection */
resp_payload->status = STATUS__InvalidArgument;
if (req->cmd_set_config->bssid.len != 0 &&
req->cmd_set_config->bssid.len != sizeof(req_data.bssid)) {
ESP_LOGD(TAG, "Received invalid BSSID");
} else if (req->cmd_set_config->ssid.len >= sizeof(req_data.ssid)) {
ESP_LOGD(TAG, "Received invalid SSID");
} else if (req->cmd_set_config->passphrase.len >= sizeof(req_data.password)) {
ESP_LOGD(TAG, "Received invalid Passphrase");
} else {
/* The received SSID and Passphrase are not NULL terminated so
* we memcpy over zeroed out arrays. Above length checks ensure
* that there is atleast 1 extra byte for null termination */
memcpy(req_data.ssid, req->cmd_set_config->ssid.data,
req->cmd_set_config->ssid.len);
memcpy(req_data.password, req->cmd_set_config->passphrase.data,
req->cmd_set_config->passphrase.len);
memcpy(req_data.bssid, req->cmd_set_config->bssid.data,
req->cmd_set_config->bssid.len);
req_data.channel = req->cmd_set_config->channel;
if (h->set_config_handler(&req_data, &h->ctx) == ESP_OK) {
resp_payload->status = STATUS__Success;
} else {
resp_payload->status = STATUS__InternalError;
}
}
resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_SET_CONFIG;
@ -188,7 +209,7 @@ static esp_err_t cmd_apply_config_handler(WiFiConfigPayload *req,
if (h->apply_config_handler(&h->ctx) == ESP_OK) {
resp_payload->status = STATUS__Success;
} else {
resp_payload->status = STATUS__InvalidArgument;
resp_payload->status = STATUS__InternalError;
}
resp->payload_case = WI_FI_CONFIG_PAYLOAD__PAYLOAD_RESP_APPLY_CONFIG;

View File

@ -80,7 +80,7 @@ Make sure to note down the BLE device name (starting with PROV_) displayed in th
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC
python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
@ -150,7 +150,6 @@ Or, enable `Reset Provisioning` option under `Example Configuration` under menuc
If the platform requirement, for running `esp_prov` is not satisfied, then the script execution will fallback to console mode, in which case the full process (involving user inputs) will look like this :
```
==== Esp_Prov Version: V0.1 ====
BLE client is running in console mode
This could be due to your platform not being supported or dependencies not being met
Please ensure all pre-requisites are met to run the full fledged client

View File

@ -76,7 +76,7 @@ def test_examples_provisioning_ble(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, None, devname)
transport = esp_prov.get_transport(provmode, devname)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -98,10 +98,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data,
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
/* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard).
* But this doesn't guarantee that the saved SSID will be null terminated, because
* wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character).
* Although, this is not a matter for concern because esp_wifi library reads the SSID
* upto 32 bytes in absence of null termination */
strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid));
strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password));
return ESP_OK;
}

View File

@ -65,14 +65,12 @@ I (398) app_prov: Console provisioning started
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport console
python esp_prov.py --transport console --proto_ver "V0.1" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
A console will open up and the `Client->Device` commands have to be copied manually to the serial monitor console prompt :
```
==== Esp_Prov Version: V0.1 ====
==== Verifying protocol version ====
Client->Device msg : proto-ver 0 56302e31
Enter device->client msg :
@ -111,8 +109,6 @@ This is helpful in understanding the provisioning process and the order in which
The full execution sequence of `esp_prov`, as seen on the console, is shown here :
```
==== Esp_Prov Version: V0.1 ====
==== Verifying protocol version ====
Client->Device msg : proto-ver 0 56302e31
Enter device->client msg : 53554343455353

View File

@ -98,10 +98,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data,
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
/* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard).
* But this doesn't guarantee that the saved SSID will be null terminated, because
* wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character).
* Although, this is not a matter for concern because esp_wifi library reads the SSID
* upto 32 bytes in absence of null termination */
strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid));
strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password));
return ESP_OK;
}

View File

@ -68,7 +68,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace the values corresponding to the parameters `--custom_info` and `--custom_ver` with your desired values for the custom configuration). Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 0 --transport softap --softap_endpoint 192.168.4.1:80 --custom_config --custom_info "some string" --custom_ver 4321
python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 0 --ssid myssid --passphrase mypassword --custom_config --custom_info "some string" --custom_ver 4321
```
Above command will perform the provisioning steps, and the monitor log should display something like this :

View File

@ -327,13 +327,13 @@ static esp_err_t start_wifi_ap(const char *ssid, const char *pass)
};
strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
wifi_config.ap.ssid_len = strlen(ssid);
wifi_config.ap.ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid));
if (strlen(pass) == 0) {
memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
} else {
strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
}

View File

@ -110,10 +110,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data,
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
/* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard).
* But this doesn't guarantee that the saved SSID will be null terminated, because
* wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character).
* Although, this is not a matter for concern because esp_wifi library reads the SSID
* upto 32 bytes in absence of null termination */
strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid));
strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password));
return ESP_OK;
}

View File

@ -74,10 +74,10 @@ I (1045) wifi_prov_mgr: Provisioning started with service name : PROV_261FCC
Make sure to note down the BLE device name (starting with `PROV_`) displayed in the serial monitor log (eg. PROV_261FCC). This will depend on the MAC ID and will be unique for every device.
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration :
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (make sure to replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). Assuming default example configuration, which uses protocomm security scheme 1 and proof of possession PoP based authentication :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport ble --ble_devname PROV_261FCC
python esp_prov.py --transport ble --service_name PROV_261FCC --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :
@ -109,6 +109,54 @@ I (54355) app: Hello World!
I (55355) app: Hello World!
```
### Wi-Fi Scanning
Provisioning manager also supports providing real-time Wi-Fi scan results (performed on the device) during provisioning. This allows the client side applications to choose the AP for which the device Wi-Fi station is to be configured. Various information about the visible APs is available, like signal strength (RSSI) and security type, etc. Also, the manager now provides capabilities information which can be used by client applications to determine the security type and availability of specific features (like `wifi_scan`).
When using the scan based provisioning, we don't need to specify the `--ssid` and `--passphrase` fields explicitly:
```
python esp_prov.py --transport ble --service_name PROV_261FCC --pop abcd1234
```
See below the sample output from `esp_prov` tool on running above command:
```
Connecting...
Connected
Getting Services...
Security scheme determined to be : 1
==== Starting Session ====
==== Session Established ====
==== Scanning Wi-Fi APs ====
++++ Scan process executed in 1.9967520237 sec
++++ Scan results : 5
++++ Scan finished in 2.7374596596 sec
==== Wi-Fi Scan results ====
S.N. SSID BSSID CHN RSSI AUTH
[ 1] MyHomeWiFiAP 788a20841996 1 -45 WPA2_PSK
[ 2] MobileHotspot 7a8a20841996 11 -46 WPA2_PSK
[ 3] MyHomeWiFiAP 788a208daa26 11 -54 WPA2_PSK
[ 4] NeighborsWiFiAP 8a8a20841996 6 -61 WPA2_PSK
[ 5] InsecureWiFiAP dca4caf1227c 7 -74 Open
Select AP by number (0 to rescan) : 1
Enter passphrase for MyHomeWiFiAP :
==== Sending Wi-Fi credential to esp32 ====
==== Wi-Fi Credentials sent successfully ====
==== Applying config to esp32 ====
==== Apply config sent successfully ====
==== Wi-Fi connection state ====
++++ WiFi state: connected ++++
==== Provisioning was successful ====
```
## Troubleshooting
### Provisioning failed

View File

@ -76,7 +76,7 @@ def test_examples_wifi_prov_mgr(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, None, devname)
transport = esp_prov.get_transport(provmode, devname)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -81,7 +81,7 @@ I (519482) tcpip_adapter: softAP assign IP to station,IP is: 192.168.4.2
In a separate terminal run the `esp_prov.py` script under `$IDP_PATH/tools/esp_prov` directory (please replace `myssid` and `mypassword` with the credentials of the AP to which the device is supposed to connect to after provisioning). The SoftAP endpoint corresponds to the IP and port of the device on the SoftAP network, but this is usually same as the default value and may be left out. Assuming default example configuration, the script should be run as follows :
```
python esp_prov.py --ssid myssid --passphrase mypassword --sec_ver 1 --pop abcd1234 --transport softap --softap_endpoint 192.168.4.1:80
python esp_prov.py --transport softap --service_name "192.168.4.1:80" --sec_ver 1 --pop abcd1234 --ssid myssid --passphrase mypassword
```
Above command will perform the provisioning steps, and the monitor log should display something like this :

View File

@ -314,13 +314,13 @@ static esp_err_t start_wifi_ap(const char *ssid, const char *pass)
};
strncpy((char *) wifi_config.ap.ssid, ssid, sizeof(wifi_config.ap.ssid));
wifi_config.ap.ssid_len = strlen(ssid);
wifi_config.ap.ssid_len = strnlen(ssid, sizeof(wifi_config.ap.ssid));
if (strlen(pass) == 0) {
memset(wifi_config.ap.password, 0, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
} else {
strncpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
strlcpy((char *) wifi_config.ap.password, pass, sizeof(wifi_config.ap.password));
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
}

View File

@ -98,10 +98,14 @@ static esp_err_t set_config_handler(const wifi_prov_config_set_data_t *req_data,
ESP_LOGI(TAG, "WiFi Credentials Received : \n\tssid %s \n\tpassword %s",
req_data->ssid, req_data->password);
memcpy((char *) wifi_cfg->sta.ssid, req_data->ssid,
strnlen(req_data->ssid, sizeof(wifi_cfg->sta.ssid)));
memcpy((char *) wifi_cfg->sta.password, req_data->password,
strnlen(req_data->password, sizeof(wifi_cfg->sta.password)));
/* Using strncpy allows the max SSID length to be 32 bytes (as per 802.11 standard).
* But this doesn't guarantee that the saved SSID will be null terminated, because
* wifi_cfg->sta.ssid is also 32 bytes long (without extra 1 byte for null character).
* Although, this is not a matter for concern because esp_wifi library reads the SSID
* upto 32 bytes in absence of null termination */
strncpy((char *) wifi_cfg->sta.ssid, req_data->ssid, sizeof(wifi_cfg->sta.ssid));
strlcpy((char *) wifi_cfg->sta.password, req_data->password, sizeof(wifi_cfg->sta.password));
return ESP_OK;
}

View File

@ -98,7 +98,7 @@ def test_examples_provisioning_softap(env, extra_data):
raise RuntimeError("Failed to get security")
print("Getting transport")
transport = esp_prov.get_transport(provmode, softap_endpoint, None)
transport = esp_prov.get_transport(provmode, softap_endpoint)
if transport is None:
raise RuntimeError("Failed to get transport")

View File

@ -6,7 +6,7 @@
# SYNOPSIS
```
python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > --sec_ver < Security version 0 / 1 > [ Optional parameters... ]
python esp_prov.py --transport < mode of provisioning : softap \ ble \ console > [ Optional parameters... ]
```
# DESCRIPTION
@ -53,11 +53,9 @@ Usage of `esp-prov` assumes that the provisioning app has specific protocomm end
* `--pop <Proof of possession string>` (Optional)
For specifying optional Proof of Possession string to use for protocomm endpoint security version 1. This option is ignored when security version 0 is in use
* `--softap_endpoint <softap_ip:port>` (Optional) (Default `192.168.4.1:80`)
For specifying the IP and port of the HTTP server on which provisioning app is running. The client must connect to the device SoftAP prior to running `esp_prov`
* `--ble_devname <BLE device name>` (Optional)
For specifying name of the BLE device to which connection is to be established prior to starting provisioning process. This is only used when `--transport ble` is specified, else it is ignored. Since connection with BLE is supported only on Linux, so this option is again ignored for other platforms
* `--service_name <name> (Optional)
When transport mode is ble, this specifies the BLE device name to which connection is to be established for provisioned.
When transport mode is softap, this specifies the HTTP server hostname / IP which is running the provisioning service, on the SoftAP network of the device which is to be provisioned. This defaults to `192.168.4.1:80` if not specified
* `--custom_config` (Optional)
This flag assumes the provisioning app has an endpoint called `custom-config`. Use `--custom_info` and `--custom_ver` options to specify the fields accepted by this endpoint

View File

@ -18,6 +18,7 @@
from __future__ import print_function
from builtins import input
import argparse
import textwrap
import time
import os
import sys
@ -57,12 +58,16 @@ def get_security(secver, pop=None, verbose=False):
return None
def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
def get_transport(sel_transport, service_name):
try:
tp = None
if (sel_transport == 'softap'):
tp = transport.Transport_Softap(softap_endpoint)
if service_name is None:
service_name = '192.168.4.1:80'
tp = transport.Transport_HTTP(service_name)
elif (sel_transport == 'ble'):
if service_name is None:
raise RuntimeError('"--service_name" must be specified for ble transport')
# BLE client is now capable of automatically figuring out
# the primary service from the advertisement data and the
# characteristics corresponding to each endpoint.
@ -71,7 +76,7 @@ def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
# in which case, the automated discovery will fail and the client
# will fallback to using the provided UUIDs instead
nu_lookup = {'prov-session': 'ff51', 'prov-config': 'ff52', 'proto-ver': 'ff53'}
tp = transport.Transport_BLE(devname=ble_devname,
tp = transport.Transport_BLE(devname=service_name,
service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
nu_lookup=nu_lookup)
elif (sel_transport == 'console'):
@ -93,31 +98,53 @@ def version_match(tp, protover, verbose=False):
if response.lower() == protover.lower():
return True
# Else interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
if info['prov']['ver'].lower() == protover.lower():
return True
try:
# Else interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
if info['prov']['ver'].lower() == protover.lower():
return True
except ValueError:
# If decoding as JSON fails, it means that capabilities
# are not supported
return False
return False
except Exception as e:
on_except(e)
return None
def has_capability(tp, capability, verbose=False):
def has_capability(tp, capability='none', verbose=False):
# Note : default value of `capability` argument cannot be empty string
# because protocomm_httpd expects non zero content lengths
try:
response = tp.send_data('proto-ver', capability)
if verbose:
print("proto-ver response : ", response)
info = json.loads(response)
if capability in info['prov']['cap']:
return True
try:
# Interpret this as JSON structure containing
# information with versions and capabilities of both
# provisioning service and application
info = json.loads(response)
supported_capabilities = info['prov']['cap']
if capability.lower() == 'none':
# No specific capability to check, but capabilities
# feature is present so return True
return True
elif capability in supported_capabilities:
return True
return False
except Exception as e:
except ValueError:
# If decoding as JSON fails, it means that capabilities
# are not supported
return False
except RuntimeError as e:
on_except(e)
return False
@ -239,75 +266,123 @@ def get_wifi_config(tp, sec):
return None
def desc_format(*args):
desc = ''
for arg in args:
desc += textwrap.fill(replace_whitespace=False, text=arg) + "\n"
return desc
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Generate ESP prov payload")
parser = argparse.ArgumentParser(description=desc_format(
'ESP Provisioning tool for configuring devices '
'running protocomm based provisioning service.',
'See esp-idf/examples/provisioning for sample applications'),
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument("--ssid", dest='ssid', type=str,
help="SSID of Wi-Fi Network", default='')
parser.add_argument("--passphrase", dest='passphrase', type=str,
help="Passphrase of Wi-Fi network", default='')
parser.add_argument("--transport", required=True, dest='mode', type=str,
help=desc_format(
'Mode of transport over which provisioning is to be performed.',
'This should be one of "softap", "ble" or "console"'))
parser.add_argument("--sec_ver", dest='secver', type=int,
help="Security scheme version", default=None)
parser.add_argument("--proto_ver", dest='protover', type=str,
help="Protocol version", default='')
parser.add_argument("--pop", dest='pop', type=str,
help="Proof of possession", default='')
parser.add_argument("--service_name", dest='name', type=str,
help=desc_format(
'This specifies the name of the provisioning service to connect to, '
'depending upon the mode of transport :',
'\t- transport "ble" : The BLE Device Name',
'\t- transport "softap" : HTTP Server hostname or IP',
'\t (default "192.168.4.1:80")'))
parser.add_argument("--softap_endpoint", dest='softap_endpoint', type=str,
help="<softap_ip:port>, http(s):// shouldn't be included", default='192.168.4.1:80')
parser.add_argument("--proto_ver", dest='version', type=str, default='',
help=desc_format(
'This checks the protocol version of the provisioning service running '
'on the device before initiating Wi-Fi configuration'))
parser.add_argument("--ble_devname", dest='ble_devname', type=str,
help="BLE Device Name", default='')
parser.add_argument("--sec_ver", dest='secver', type=int, default=None,
help=desc_format(
'Protocomm security scheme used by the provisioning service for secure '
'session establishment. Accepted values are :',
'\t- 0 : No security',
'\t- 1 : X25519 key exchange + AES-CTR encryption',
'\t + Authentication using Proof of Possession (PoP)',
'In case device side application uses IDF\'s provisioning manager, '
'the compatible security version is automatically determined from '
'capabilities retrieved via the version endpoint'))
parser.add_argument("--transport", dest='provmode', type=str,
help="provisioning mode i.e console or softap or ble", default='softap')
parser.add_argument("--pop", dest='pop', type=str, default='',
help=desc_format(
'This specifies the Proof of possession (PoP) when security scheme 1 '
'is used'))
parser.add_argument("--custom_config", help="Provision Custom Configuration",
action="store_true")
parser.add_argument("--custom_info", dest='custom_info', type=str,
help="Custom Config Info String", default='<some custom info string>')
parser.add_argument("--custom_ver", dest='custom_ver', type=int,
help="Custom Config Version Number", default=2)
parser.add_argument("--ssid", dest='ssid', type=str, default='',
help=desc_format(
'This configures the device to use SSID of the Wi-Fi network to which '
'we would like it to connect to permanently, once provisioning is complete. '
'If Wi-Fi scanning is supported by the provisioning service, this need not '
'be specified'))
parser.add_argument("--passphrase", dest='passphrase', type=str, default='',
help=desc_format(
'This configures the device to use Passphrase for the Wi-Fi network to which '
'we would like it to connect to permanently, once provisioning is complete. '
'If Wi-Fi scanning is supported by the provisioning service, this need not '
'be specified'))
parser.add_argument("--custom_config", action="store_true",
help=desc_format(
'This is an optional parameter, only intended for use with '
'"examples/provisioning/custom_config"'))
parser.add_argument("--custom_info", dest='custom_info', type=str, default='<some custom info string>',
help=desc_format(
'Custom Config Info String. "--custom_config" must be specified for using this'))
parser.add_argument("--custom_ver", dest='custom_ver', type=int, default=2,
help=desc_format(
'Custom Config Version Number. "--custom_config" must be specified for using this'))
parser.add_argument("-v","--verbose", help="Increase output verbosity", action="store_true")
parser.add_argument("-v","--verbose", help="increase output verbosity", action="store_true")
args = parser.parse_args()
if args.protover != '':
print("==== Esp_Prov Version: " + args.protover + " ====")
obj_transport = get_transport(args.provmode, args.softap_endpoint, args.ble_devname)
obj_transport = get_transport(args.mode.lower(), args.name)
if obj_transport is None:
print("---- Invalid provisioning mode ----")
print("---- Failed to establish connection ----")
exit(1)
# If security version not specified check in capabilities
if args.secver is None:
# First check if capabilities are supported or not
if not has_capability(obj_transport):
print('Security capabilities could not be determined. Please specify "--sec_ver" explicitly')
print("---- Invalid Security Version ----")
exit(2)
# When no_sec is present, use security 0, else security 1
args.secver = int(not has_capability(obj_transport, 'no_sec'))
print("Security scheme determined to be :", args.secver)
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
if len(args.pop) == 0:
print("---- Proof of Possession argument not provided ----")
exit(2)
elif len(args.pop) != 0:
print("---- Proof of Possession will be ignored ----")
args.pop = ''
if (args.secver != 0) and not has_capability(obj_transport, 'no_pop'):
if len(args.pop) == 0:
print("---- Proof of Possession argument not provided ----")
exit(2)
elif len(args.pop) != 0:
print("---- Proof of Possession will be ignored ----")
args.pop = ''
obj_security = get_security(args.secver, args.pop, args.verbose)
if obj_security is None:
print("---- Invalid Security Version ----")
exit(2)
if args.protover != '':
if args.version != '':
print("\n==== Verifying protocol version ====")
if not version_match(obj_transport, args.protover, args.verbose):
if not version_match(obj_transport, args.version, args.verbose):
print("---- Error in protocol version matching ----")
exit(3)
print("==== Verified protocol version successfully ====")
print("\n==== Starting Session ====")
if not establish_session(obj_transport, obj_security):
print("Failed to establish session. Ensure that security scheme and proof of possession are correct")
print("---- Error in establishing session ----")
exit(4)
print("==== Session Established ====")
@ -328,7 +403,7 @@ if __name__ == '__main__':
while True:
print("\n==== Scanning Wi-Fi APs ====")
start_time = time.time()
APs = scan_wifi_APs(args.provmode, obj_transport, obj_security)
APs = scan_wifi_APs(args.mode.lower(), obj_transport, obj_security)
end_time = time.time()
print("\n++++ Scan finished in " + str(end_time - start_time) + " sec")
if APs is None:

View File

@ -14,5 +14,5 @@
#
from .transport_console import * # noqa: F403, F401
from .transport_softap import * # noqa: F403, F401
from .transport_http import * # noqa: F403, F401
from .transport_ble import * # noqa: F403, F401

View File

@ -16,14 +16,30 @@
from __future__ import print_function
from future.utils import tobytes
import socket
import http.client
import ssl
from .transport import Transport
class Transport_Softap(Transport):
def __init__(self, url):
self.conn = http.client.HTTPConnection(url, timeout=30)
class Transport_HTTP(Transport):
def __init__(self, hostname, certfile=None):
try:
socket.gethostbyname(hostname.split(':')[0])
except socket.gaierror:
raise RuntimeError("Unable to resolve hostname :" + hostname)
if certfile is None:
self.conn = http.client.HTTPConnection(hostname, timeout=30)
else:
ssl_ctx = ssl.create_default_context(cafile=certfile)
self.conn = http.client.HTTPSConnection(hostname, context=ssl_ctx, timeout=30)
try:
print("Connecting to " + hostname)
self.conn.connect()
except Exception as err:
raise RuntimeError("Connection Failure : " + str(err))
self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
def _send_post_request(self, path, data):