Merge branch 'feature/esp-https-ota-basic-auth' into 'master'

esp_https_ota: component refactoring, bugfixes and feature additions

See merge request idf/esp-idf!4245
This commit is contained in:
Angus Gratton 2019-05-10 12:39:14 +08:00
commit b6a24bbd25
12 changed files with 738 additions and 145 deletions

View File

@ -7,4 +7,11 @@ menu "ESP HTTP client"
help
This option will enable https protocol by linking mbedtls library and initializing SSL transport
config ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
bool "Enable HTTP Basic Authentication"
default n
help
This option will enable HTTP Basic Authentication. It is disabled by default as Basic
auth uses unencrypted encoding, so it introduces a vulnerability when not using TLS
endmenu

View File

@ -152,20 +152,6 @@ static const char *HTTP_METHOD_MAPPING[] = {
"OPTIONS"
};
/**
* Enum for the HTTP status codes.
*/
enum HttpStatus_Code
{
/* 3xx - Redirection */
HttpStatus_MovedPermanently = 301,
HttpStatus_Found = 302,
/* 4xx - Client Error */
HttpStatus_Unauthorized = 401
};
static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, int write_len);
static esp_err_t esp_http_client_connect(esp_http_client_handle_t client);
static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client);
@ -617,13 +603,12 @@ esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client)
if (client->location == NULL) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "Redirect to %s", client->location);
return esp_http_client_set_url(client, client->location);
}
static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
{
char *auth_header = NULL;
if (client->redirect_counter >= client->max_redirection_count || client->disable_auto_redirect) {
ESP_LOGE(TAG, "Error, reach max_redirection_count count=%d", client->redirect_counter);
return ESP_ERR_HTTP_MAX_REDIRECT;
@ -631,44 +616,12 @@ static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
switch (client->response->status_code) {
case HttpStatus_MovedPermanently:
case HttpStatus_Found:
ESP_LOGI(TAG, "Redirect to %s", client->location);
esp_http_client_set_url(client, client->location);
esp_http_client_set_redirection(client);
client->redirect_counter ++;
client->process_again = 1;
break;
case HttpStatus_Unauthorized:
auth_header = client->auth_header;
if (auth_header) {
http_utils_trim_whitespace(&auth_header);
ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
client->redirect_counter ++;
if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
ESP_LOGD(TAG, "type = Digest");
client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
} else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
ESP_LOGD(TAG, "type = Basic");
client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
break;
}
_clear_auth_data(client);
client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
client->auth_data->nc = 1;
client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
client->process_again = 1;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
}
esp_http_client_add_auth(client);
}
return ESP_OK;
}
@ -1250,3 +1203,48 @@ esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_h
return HTTP_TRANSPORT_UNKNOWN;
}
}
void esp_http_client_add_auth(esp_http_client_handle_t client)
{
if (client == NULL) {
return;
}
if (client->state != HTTP_STATE_RES_COMPLETE_HEADER) {
return;
}
char *auth_header = client->auth_header;
if (auth_header) {
http_utils_trim_whitespace(&auth_header);
ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
client->redirect_counter++;
if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
ESP_LOGD(TAG, "type = Digest");
client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
} else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
ESP_LOGD(TAG, "type = Basic");
client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
#endif
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
return;
}
_clear_auth_data(client);
client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
client->auth_data->nc = 1;
client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
client->process_again = 1;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
}
}

View File

@ -122,6 +122,17 @@ typedef struct {
bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */
} esp_http_client_config_t;
/**
* Enum for the HTTP status codes.
*/
typedef enum {
/* 3xx - Redirection */
HttpStatus_MovedPermanently = 301,
HttpStatus_Found = 302,
/* 4xx - Client Error */
HttpStatus_Unauthorized = 401
} HttpStatus_Code;
#define ESP_ERR_HTTP_BASE (0x7000) /*!< Starting number of HTTP error codes */
#define ESP_ERR_HTTP_MAX_REDIRECT (ESP_ERR_HTTP_BASE + 1) /*!< The error exceeds the number of HTTP redirects */
@ -415,12 +426,23 @@ esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_h
*
* @param[in] client The esp_http_client handle
*
* @return
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client);
/**
* @brief On receiving HTTP Status code 401, this API can be invoked to add authorization
* information.
*
* @note There is a possibility of receiving body message with redirection status codes, thus make sure
* to flush off body data after calling this API.
*
* @param[in] client The esp_http_client handle
*/
void esp_http_client_add_auth(esp_http_client_handle_t client);
#ifdef __cplusplus
}
#endif

View File

@ -15,20 +15,40 @@
#pragma once
#include <esp_http_client.h>
#include <esp_ota_ops.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void *esp_https_ota_handle_t;
/**
* @brief ESP HTTPS OTA configuration
*/
typedef struct {
const esp_http_client_config_t *http_config; /*!< ESP HTTP client configuration */
} esp_https_ota_config_t;
#define ESP_ERR_HTTPS_OTA_BASE (0x9000)
#define ESP_ERR_HTTPS_OTA_IN_PROGRESS (ESP_ERR_HTTPS_OTA_BASE + 1) /* OTA operation in progress */
/**
* @brief HTTPS OTA Firmware upgrade.
*
* This function performs HTTPS OTA Firmware upgrade
*
* This function allocates HTTPS OTA Firmware upgrade context, establishes HTTPS connection,
* reads image data from HTTP stream and writes it to OTA partition and
* finishes HTTPS OTA Firmware upgrade operation.
* This API supports URL redirection, but if CA cert of URLs differ then it
* should be appended to `cert_pem` member of `config`.
*
* @param[in] config pointer to esp_http_client_config_t structure.
*
* @note For secure HTTPS updates, the `cert_pem` member of `config`
* structure must be set to the server certificate.
* @note This API handles the entire OTA operation, so if this API is being used
* then no other APIs from `esp_https_ota` component should be called.
* If more information and control is needed during the HTTPS OTA process,
* then one can use `esp_https_ota_begin` and subsequent APIs. If this API returns
* successfully, esp_restart() must be called to boot from the new firmware image.
*
* @return
* - ESP_OK: OTA data updated, next reboot will use specified partition.
@ -41,6 +61,107 @@ extern "C" {
*/
esp_err_t esp_https_ota(const esp_http_client_config_t *config);
/**
* @brief Start HTTPS OTA Firmware upgrade
*
* This function initializes ESP HTTPS OTA context and establishes HTTPS connection.
* This function must be invoked first. If this function returns successfully, then `esp_https_ota_perform` should be
* called to continue with the OTA process and there should be a call to `esp_https_ota_finish` on
* completion of OTA operation or on failure in subsequent operations.
* This API supports URL redirection, but if CA cert of URLs differ then it
* should be appended to `cert_pem` member of `http_config`, which is a part of `ota_config`.
*
* @param[in] ota_config pointer to esp_https_ota_config_t structure
* @param[out] handle pointer to an allocated data of type `esp_https_ota_handle_t`
* which will be initialised in this function
*
* @note This API is blocking, so setting `is_async` member of `http_config` structure will
* result in an error.
*
* @return
* - ESP_OK: HTTPS OTA Firmware upgrade context initialised and HTTPS connection established
* - ESP_FAIL: For generic failure.
* - ESP_ERR_INVALID_ARG: Invalid argument (missing/incorrect config, certificate, etc.)
* - For other return codes, refer documentation in app_update component and esp_http_client
* component in esp-idf.
*/
esp_err_t esp_https_ota_begin(esp_https_ota_config_t *ota_config, esp_https_ota_handle_t *handle);
/**
* @brief Read image data from HTTP stream and write it to OTA partition
*
* This function reads image data from HTTP stream and writes it to OTA partition. This function
* must be called only if esp_https_ota_begin() returns successfully.
* This function must be called in a loop since it returns after every HTTP read operation thus
* giving you the flexibility to stop OTA operation midway.
*
* @param[in] https_ota_handle pointer to esp_https_ota_handle_t structure
*
* @return
* - ESP_ERR_HTTPS_OTA_IN_PROGRESS: OTA update is in progress, call this API again to continue.
* - ESP_OK: OTA update was successful
* - ESP_FAIL: OTA update failed
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_OTA_VALIDATE_FAILED: Invalid app image
* - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation.
* - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.
* - For other return codes, refer OTA documentation in esp-idf's app_update component.
*/
esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle);
/**
* @brief Clean-up HTTPS OTA Firmware upgrade and close HTTPS connection
*
* This function closes the HTTP connection and frees the ESP HTTPS OTA context.
* This function switches the boot partition to the OTA partition containing the
* new firmware image.
*
* @note If this API returns successfully, esp_restart() must be called to
* boot from the new firmware image
*
* @param[in] https_ota_handle pointer to esp_https_ota_handle_t structure
*
* @return
* - ESP_OK: Clean-up successful
* - ESP_ERR_INVALID_STATE
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_OTA_VALIDATE_FAILED: Invalid app image
*/
esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle);
/**
* @brief Reads app description from image header. The app description provides information
* like the "Firmware version" of the image.
*
* @note This API can be called only after esp_https_ota_begin() and before esp_https_ota_perform().
* Calling this API is not mandatory.
*
* @param[in] https_ota_handle pointer to esp_https_ota_handle_t structure
* @param[out] new_app_info pointer to an allocated esp_app_desc_t structure
*
* @return
* - ESP_ERR_INVALID_ARG: Invalid arguments
* - ESP_FAIL: Failed to read image descriptor
* - ESP_OK: Successfully read image descriptor
*/
esp_err_t esp_https_ota_get_img_desc(esp_https_ota_handle_t https_ota_handle, esp_app_desc_t *new_app_info);
/*
* @brief This function returns OTA image data read so far.
*
* @note This API should be called only if `esp_https_ota_perform()` has been called atleast once or
* if `esp_https_ota_get_img_desc` has been called before.
*
* @param[in] https_ota_handle pointer to esp_https_ota_handle_t structure
*
* @return
* - -1 On failure
* - total bytes read so far
*/
int esp_https_ota_get_image_len_read(esp_https_ota_handle_t https_ota_handle);
#ifdef __cplusplus
}
#endif

View File

@ -16,121 +16,339 @@
#include <stdlib.h>
#include <string.h>
#include <esp_https_ota.h>
#include <esp_ota_ops.h>
#include <esp_log.h>
#define DEFAULT_OTA_BUF_SIZE 256
#define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1
#define DEFAULT_OTA_BUF_SIZE IMAGE_HEADER_SIZE
static const char *TAG = "esp_https_ota";
static void http_cleanup(esp_http_client_handle_t client)
typedef enum {
ESP_HTTPS_OTA_INIT,
ESP_HTTPS_OTA_BEGIN,
ESP_HTTPS_OTA_IN_PROGRESS,
ESP_HTTPS_OTA_SUCCESS,
} esp_https_ota_state;
struct esp_https_ota_handle {
esp_ota_handle_t update_handle;
const esp_partition_t *update_partition;
esp_http_client_handle_t http_client;
char *ota_upgrade_buf;
size_t ota_upgrade_buf_size;
int binary_file_len;
esp_https_ota_state state;
};
typedef struct esp_https_ota_handle esp_https_ota_t;
static bool process_again(int status_code)
{
switch (status_code) {
case HttpStatus_MovedPermanently:
case HttpStatus_Found:
case HttpStatus_Unauthorized:
return true;
default:
return false;
}
return false;
}
static esp_err_t _http_handle_response_code(esp_http_client_handle_t http_client, int status_code)
{
esp_err_t err;
if (status_code == HttpStatus_MovedPermanently || status_code == HttpStatus_Found) {
err = esp_http_client_set_redirection(http_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "URL redirection Failed");
return err;
}
} else if (status_code == HttpStatus_Unauthorized) {
esp_http_client_add_auth(http_client);
}
char upgrade_data_buf[DEFAULT_OTA_BUF_SIZE];
if (process_again(status_code)) {
while (1) {
int data_read = esp_http_client_read(http_client, upgrade_data_buf, DEFAULT_OTA_BUF_SIZE);
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
return ESP_FAIL;
} else if (data_read == 0) {
return ESP_OK;
}
}
}
return ESP_OK;
}
static esp_err_t _http_connect(esp_http_client_handle_t http_client)
{
esp_err_t err = ESP_FAIL;
int status_code;
do {
err = esp_http_client_open(http_client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return err;
}
esp_http_client_fetch_headers(http_client);
status_code = esp_http_client_get_status_code(http_client);
if (_http_handle_response_code(http_client, status_code) != ESP_OK) {
return ESP_FAIL;
}
} while (process_again(status_code));
return err;
}
static void _http_cleanup(esp_http_client_handle_t client)
{
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
static esp_err_t _ota_write(esp_https_ota_t *https_ota_handle, const void *buffer, size_t buf_len)
{
if (buffer == NULL || https_ota_handle == NULL) {
return ESP_FAIL;
}
esp_err_t err = esp_ota_write(https_ota_handle->update_handle, buffer, buf_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%d", err);
} else {
https_ota_handle->binary_file_len += buf_len;
ESP_LOGD(TAG, "Written image length %d", https_ota_handle->binary_file_len);
err = ESP_ERR_HTTPS_OTA_IN_PROGRESS;
}
return err;
}
esp_err_t esp_https_ota_begin(esp_https_ota_config_t *ota_config, esp_https_ota_handle_t *handle)
{
esp_err_t err;
if (handle == NULL || ota_config == NULL || ota_config->http_config == NULL) {
ESP_LOGE(TAG, "esp_https_ota_begin: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
#if !CONFIG_OTA_ALLOW_HTTP
if (!ota_config->http_config->cert_pem) {
ESP_LOGE(TAG, "Server certificate not found in esp_http_client config");
return ESP_ERR_INVALID_ARG;
}
#endif
esp_https_ota_t *https_ota_handle = calloc(1, sizeof(esp_https_ota_t));
if (!https_ota_handle) {
ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
return ESP_ERR_NO_MEM;
}
/* Initiate HTTP Connection */
https_ota_handle->http_client = esp_http_client_init(ota_config->http_config);
if (https_ota_handle->http_client == NULL) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
err = ESP_FAIL;
goto failure;
}
err = _http_connect(https_ota_handle->http_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to establish HTTP connection");
goto http_cleanup;
}
https_ota_handle->update_partition = NULL;
ESP_LOGI(TAG, "Starting OTA...");
https_ota_handle->update_partition = esp_ota_get_next_update_partition(NULL);
if (https_ota_handle->update_partition == NULL) {
ESP_LOGE(TAG, "Passive OTA partition not found");
err = ESP_FAIL;
goto http_cleanup;
}
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
https_ota_handle->update_partition->subtype, https_ota_handle->update_partition->address);
const int alloc_size = (ota_config->http_config->buffer_size > DEFAULT_OTA_BUF_SIZE) ?
ota_config->http_config->buffer_size : DEFAULT_OTA_BUF_SIZE;
https_ota_handle->ota_upgrade_buf = (char *)malloc(alloc_size);
if (!https_ota_handle->ota_upgrade_buf) {
ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
err = ESP_ERR_NO_MEM;
goto http_cleanup;
}
https_ota_handle->ota_upgrade_buf_size = alloc_size;
https_ota_handle->binary_file_len = 0;
*handle = (esp_https_ota_handle_t)https_ota_handle;
https_ota_handle->state = ESP_HTTPS_OTA_BEGIN;
return ESP_OK;
http_cleanup:
_http_cleanup(https_ota_handle->http_client);
failure:
free(https_ota_handle);
return err;
}
esp_err_t esp_https_ota_get_img_desc(esp_https_ota_handle_t https_ota_handle, esp_app_desc_t *new_app_info)
{
esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
if (handle == NULL || new_app_info == NULL) {
ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
if (handle->state < ESP_HTTPS_OTA_BEGIN) {
ESP_LOGE(TAG, "esp_https_ota_read_img_desc: Invalid state");
return ESP_FAIL;
}
int data_read_size = IMAGE_HEADER_SIZE;
int data_read = esp_http_client_read(handle->http_client,
handle->ota_upgrade_buf,
data_read_size);
if (data_read < 0) {
return ESP_FAIL;
}
if (data_read >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
memcpy(new_app_info, &handle->ota_upgrade_buf[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
handle->binary_file_len += data_read;
} else {
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
{
esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
if (handle == NULL) {
ESP_LOGE(TAG, "esp_https_ota_perform: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
if (handle->state < ESP_HTTPS_OTA_BEGIN) {
ESP_LOGE(TAG, "esp_https_ota_perform: Invalid state");
return ESP_FAIL;
}
esp_err_t err;
int data_read;
switch (handle->state) {
case ESP_HTTPS_OTA_BEGIN:
err = esp_ota_begin(handle->update_partition, OTA_SIZE_UNKNOWN, &handle->update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (%s)", esp_err_to_name(err));
return err;
}
handle->state = ESP_HTTPS_OTA_IN_PROGRESS;
/* In case `esp_https_ota_read_img_desc` was invoked first,
then the image data read there should be written to OTA partition
*/
if (handle->binary_file_len) {
return _ota_write(handle, (const void *)handle->ota_upgrade_buf, handle->binary_file_len);
}
/* falls through */
case ESP_HTTPS_OTA_IN_PROGRESS:
data_read = esp_http_client_read(handle->http_client,
handle->ota_upgrade_buf,
handle->ota_upgrade_buf_size);
if (data_read == 0) {
ESP_LOGI(TAG, "Connection closed, all data received");
} else if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
return ESP_FAIL;
} else if (data_read > 0) {
return _ota_write(handle, (const void *)handle->ota_upgrade_buf, data_read);
}
handle->state = ESP_HTTPS_OTA_SUCCESS;
break;
default:
ESP_LOGE(TAG, "Invalid ESP HTTPS OTA State");
return ESP_FAIL;
break;
}
return ESP_OK;
}
esp_err_t esp_https_ota_finish(esp_https_ota_handle_t https_ota_handle)
{
esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (handle->state < ESP_HTTPS_OTA_BEGIN) {
return ESP_FAIL;
}
esp_err_t err = ESP_OK;
switch (handle->state) {
case ESP_HTTPS_OTA_SUCCESS:
case ESP_HTTPS_OTA_IN_PROGRESS:
err = esp_ota_end(handle->update_handle);
/* falls through */
case ESP_HTTPS_OTA_BEGIN:
free(handle->ota_upgrade_buf);
_http_cleanup(handle->http_client);
free(handle);
break;
default:
ESP_LOGE(TAG, "Invalid ESP HTTPS OTA State");
break;
}
if ((err == ESP_OK) && (handle->state == ESP_HTTPS_OTA_SUCCESS)) {
esp_err_t err = esp_ota_set_boot_partition(handle->update_partition);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%d", err);
}
}
return err;
}
int esp_https_ota_get_image_len_read(esp_https_ota_handle_t https_ota_handle)
{
esp_https_ota_t *handle = (esp_https_ota_t *)https_ota_handle;
if (handle == NULL) {
return -1;
}
if (handle->state < ESP_HTTPS_OTA_IN_PROGRESS) {
return -1;
}
return handle->binary_file_len;
}
esp_err_t esp_https_ota(const esp_http_client_config_t *config)
{
if (!config) {
ESP_LOGE(TAG, "esp_http_client config not found");
return ESP_ERR_INVALID_ARG;
}
}
#if !CONFIG_OTA_ALLOW_HTTP
if (!config->cert_pem && !config->use_global_ca_store) {
ESP_LOGE(TAG, "Server certificate not found, either through configuration or global CA store");
return ESP_ERR_INVALID_ARG;
}
#endif
esp_https_ota_config_t ota_config = {
.http_config = config,
};
esp_http_client_handle_t client = esp_http_client_init(config);
if (client == NULL) {
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
if (https_ota_handle == NULL) {
return ESP_FAIL;
}
#if !CONFIG_OTA_ALLOW_HTTP
if (esp_http_client_get_transport_type(client) != HTTP_TRANSPORT_OVER_SSL) {
ESP_LOGE(TAG, "Transport is not over HTTPS");
return ESP_FAIL;
}
#endif
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
esp_http_client_cleanup(client);
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
return err;
}
esp_http_client_fetch_headers(client);
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
ESP_LOGI(TAG, "Starting OTA...");
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
ESP_LOGE(TAG, "Passive OTA partition not found");
http_cleanup(client);
return ESP_FAIL;
}
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x",
update_partition->subtype, update_partition->address);
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err);
http_cleanup(client);
return err;
}
ESP_LOGI(TAG, "esp_ota_begin succeeded");
ESP_LOGI(TAG, "Please Wait. This may take time");
esp_err_t ota_write_err = ESP_OK;
const int alloc_size = (config->buffer_size > 0) ? config->buffer_size : DEFAULT_OTA_BUF_SIZE;
char *upgrade_data_buf = (char *)malloc(alloc_size);
if (!upgrade_data_buf) {
ESP_LOGE(TAG, "Couldn't allocate memory to upgrade data buffer");
return ESP_ERR_NO_MEM;
}
int binary_file_len = 0;
while (1) {
int data_read = esp_http_client_read(client, upgrade_data_buf, alloc_size);
if (data_read == 0) {
ESP_LOGI(TAG, "Connection closed, all data received");
err = esp_https_ota_perform(https_ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
if (data_read < 0) {
ESP_LOGE(TAG, "Error: SSL data read error");
break;
}
if (data_read > 0) {
ota_write_err = esp_ota_write(update_handle, (const void *) upgrade_data_buf, data_read);
if (ota_write_err != ESP_OK) {
break;
}
binary_file_len += data_read;
ESP_LOGD(TAG, "Written image length %d", binary_file_len);
}
}
free(upgrade_data_buf);
http_cleanup(client);
ESP_LOGD(TAG, "Total binary data length writen: %d", binary_file_len);
esp_err_t ota_end_err = esp_ota_end(update_handle);
if (ota_write_err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%d", err);
return ota_write_err;
} else if (ota_end_err != ESP_OK) {
ESP_LOGE(TAG, "Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err);
return ota_end_err;
}
err = esp_ota_set_boot_partition(update_partition);
esp_err_t ota_finish_err = esp_https_ota_finish(https_ota_handle);
free(https_ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%d", err);
/* If there was an error in esp_https_ota_perform(),
then it is given more precedence than error in esp_https_ota_finish()
*/
return err;
} else if (ota_finish_err != ESP_OK) {
return ota_finish_err;
}
ESP_LOGI(TAG, "esp_ota_set_boot_partition succeeded");
return ESP_OK;
}
}

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(advanced_https_ota)

View File

@ -0,0 +1,9 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := advanced_https_ota
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,8 @@
set(COMPONENT_SRCS "advanced_https_ota_example.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
# Embed the server root certificate into the final binary
set(COMPONENT_EMBED_TXTFILES ${IDF_PROJECT_PATH}/server_certs/ca_cert.pem)
register_component()

View File

@ -0,0 +1,21 @@
menu "Example Configuration"
config WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
config FIRMWARE_UPGRADE_URL
string "firmware upgrade url endpoint"
default "https://192.168.0.3:8070/hello-world.bin"
help
URL of server which hosts the firmware
image.
endmenu

View File

@ -0,0 +1,177 @@
/* Advanced HTTPS OTA example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "nvs.h"
#include "nvs_flash.h"
static const char *TAG = "advanced_https_ota_example";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP? */
const int CONNECTED_BIT = BIT0;
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
switch (event->event_id) {
case SYSTEM_EVENT_STA_START:
esp_wifi_connect();
break;
case SYSTEM_EVENT_STA_GOT_IP:
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
/* This is a workaround as ESP32 WiFi libs don't currently
auto-reassociate. */
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
static void initialise_wifi(void)
{
tcpip_adapter_init();
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
wifi_config_t wifi_config = {
.sta = {
.ssid = CONFIG_WIFI_SSID,
.password = CONFIG_WIFI_PASSWORD,
},
};
ESP_LOGI(TAG, "Setting WiFi configuration SSID %s", wifi_config.sta.ssid);
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
static esp_err_t validate_image_header(esp_app_desc_t *new_app_info)
{
if (new_app_info == NULL) {
return ESP_ERR_INVALID_ARG;
}
const esp_partition_t *running = esp_ota_get_running_partition();
esp_app_desc_t running_app_info;
if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
ESP_LOGI(TAG, "Running firmware version: %s", running_app_info.version);
}
if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
ESP_LOGW(TAG, "Current running version is the same as a new. We will not continue the update.");
return ESP_FAIL;
}
return ESP_OK;
}
void advanced_ota_example_task(void * pvParameter)
{
ESP_LOGI(TAG, "Starting Advanced OTA example");
xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
false, true, portMAX_DELAY);
ESP_LOGI(TAG, "Connected to WiFi network! Attempting to connect to server...");
esp_err_t ota_finish_err = ESP_OK;
esp_http_client_config_t config = {
.url = CONFIG_FIRMWARE_UPGRADE_URL,
.cert_pem = (char *)server_cert_pem_start,
};
esp_https_ota_config_t ota_config = {
.http_config = &config,
};
esp_https_ota_handle_t https_ota_handle = NULL;
esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "ESP HTTPS OTA Begin failed");
vTaskDelete(NULL);
}
esp_app_desc_t app_desc;
err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_https_ota_read_img_desc failed");
goto ota_end;
}
err = validate_image_header(&app_desc);
if (err != ESP_OK) {
ESP_LOGE(TAG, "image header verification failed");
goto ota_end;
}
while (1) {
err = esp_https_ota_perform(https_ota_handle);
if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
break;
}
// esp_https_ota_perform returns after every read operation which gives user the ability to
// monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image
// data read so far.
ESP_LOGD(TAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
}
ota_end:
ota_finish_err = esp_https_ota_finish(https_ota_handle);
if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
ESP_LOGI(TAG, "ESP_HTTPS_OTA upgrade successful. Rebooting ...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
esp_restart();
} else {
ESP_LOGE(TAG, "ESP_HTTPS_OTA upgrade failed...");
}
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main()
{
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
initialise_wifi();
xTaskCreate(&advanced_ota_example_task, "advanced_ota_example_task", 1024 * 8, NULL, 5, NULL);
}

View File

@ -0,0 +1,6 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
COMPONENT_EMBED_TXTFILES := ${PROJECT_PATH}/server_certs/ca_cert.pem