From 3e46a73964cac3958a748f06470b924c402a269f Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Tue, 20 May 2025 10:56:44 +0530 Subject: [PATCH 1/2] fix(esp_https_ota): Check and handle missing image length in OTA HTTP response --- components/esp_https_ota/src/esp_https_ota.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 2757121378..7dd2ee40a5 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -379,6 +379,11 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http } https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); + if (https_ota_handle->image_length == -1) { + ESP_LOGE(TAG, "Failed to get image length from http response"); + err = ESP_FAIL; + goto http_cleanup; + } #if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB /* In case of pre ecnrypted OTA, actual image size of OTA binary includes header size * which stored in variable enc_img_header_size */ From caf828dfaf7b0f6ba4cf5dbff5bca7e69faa48c4 Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Tue, 20 May 2025 11:05:42 +0530 Subject: [PATCH 2/2] fix(esp_http_client): Fix OTA failure with partial download enabled This commit fixes an issue encountered during OTA when partial download is enabled using an AWS signed URL restricted to GET requests. It also adds an API to retrieve the OTA image size from the Content-Range header. --- components/esp_http_client/esp_http_client.c | 30 ++++++++++++++++- .../esp_http_client/include/esp_http_client.h | 14 ++++++++ components/esp_https_ota/src/esp_https_ota.c | 33 ++++++++++++++++--- .../en/api-reference/system/esp_https_ota.rst | 3 ++ .../api-reference/system/esp_https_ota.rst | 3 ++ 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 4bf003813d..c59bd8a159 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -67,6 +67,7 @@ typedef struct { int64_t data_process; /*!< data processed */ int method; /*!< http method */ bool is_chunked; + int64_t content_range; /*!< content range */ } esp_http_data_t; typedef struct { @@ -269,7 +270,29 @@ static int http_on_header_value(http_parser *parser, const char *at, size_t leng if (client->current_header_key == NULL) { return 0; } - if (strcasecmp(client->current_header_key, "Location") == 0) { + if (strcasecmp(client->current_header_key, "Content-Range") == 0) { + HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->current_header_value, at, length), -1, TAG, "Failed to append string"); + + int64_t total_size = -1; + client->response->content_range = -1; + char *slash_pos = strchr(client->current_header_value, '/'); + + if (slash_pos) { + if (slash_pos[1] == '*') { + ESP_LOGE(TAG, "Content-Range header has unknown total size (bytes A-B/*)"); + } else { + char *endptr; + total_size = strtol(slash_pos + 1, &endptr, 10); + if (total_size > 0 && *endptr == '\0') { + client->response->content_range = total_size; + } else { + ESP_LOGE(TAG, "Failed to extract total size from Content-Range"); + } + } + } else { + ESP_LOGE(TAG, "Invalid Content-Range format (missing '/')"); + } + } else if (strcasecmp(client->current_header_key, "Location") == 0) { HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->location, at, length), -1, TAG, "Failed to append string"); } else if (strcasecmp(client->current_header_key, "Transfer-Encoding") == 0 && memcmp(at, "chunked", length) == 0) { @@ -1817,6 +1840,11 @@ int64_t esp_http_client_get_content_length(esp_http_client_handle_t client) return client->response->content_length; } +int64_t esp_http_client_get_content_range(esp_http_client_handle_t client) +{ + return client->response->content_range; +} + bool esp_http_client_is_chunked_response(esp_http_client_handle_t client) { return client->response->is_chunked; diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index 35ca5a877a..1dd420d5d3 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -232,6 +232,7 @@ typedef struct { typedef enum { /* 2xx - Success */ HttpStatus_Ok = 200, + HttpStatus_PartialContent = 206, /* 3xx - Redirection */ HttpStatus_MultipleChoices = 300, @@ -642,6 +643,19 @@ int esp_http_client_get_status_code(esp_http_client_handle_t client); */ int64_t esp_http_client_get_content_length(esp_http_client_handle_t client); +/** + * @brief Get http response content range (from header Content-Range) + * The returned value is valid only if this function is invoked after + * a successful call to `esp_http_client_perform`. + * Content-Range is set to -1 if parsing fails or if the Content-Range header is not present. + * + * @param[in] client The esp_http_client handle + * + * @return + * - Content-Range value as bytes + */ +int64_t esp_http_client_get_content_range(esp_http_client_handle_t client); + /** * @brief Close http connection, still kept all http request resources * diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 7dd2ee40a5..89390591a9 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -365,20 +365,45 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http if (https_ota_handle->partial_http_download) { esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_HEAD); + err = esp_http_client_perform(https_ota_handle->http_client); if (err == ESP_OK) { int status = esp_http_client_get_status_code(https_ota_handle->http_client); if (status != HttpStatus_Ok) { - ESP_LOGE(TAG, "Received incorrect http status %d", status); - err = ESP_FAIL; - goto http_cleanup; + // If server doesn't support HEAD request, we need to get image length from GET request + // using Range header + esp_http_client_set_header(https_ota_handle->http_client, "Range", "bytes=0-0"); + esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_GET); + + err = esp_http_client_perform(https_ota_handle->http_client); + if (err == ESP_OK) { + status = esp_http_client_get_status_code(https_ota_handle->http_client); + if (status != HttpStatus_Ok && status != HttpStatus_PartialContent) { + ESP_LOGE(TAG, "Received incorrect http status %d", status); + err = ESP_FAIL; + goto http_cleanup; + } + } else { + ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); + goto http_cleanup; + } + esp_http_client_set_header(https_ota_handle->http_client, "Range", NULL); + + if (status == HttpStatus_Ok) { + // If server responds with 200 OK, we can get image length from content-length header + https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); + } else { + // If server responds with 206 Partial Content, we can get image length from content-range header + https_ota_handle->image_length = esp_http_client_get_content_range(https_ota_handle->http_client); + } + } else { + https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); } } else { ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); goto http_cleanup; } - https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); if (https_ota_handle->image_length == -1) { ESP_LOGE(TAG, "Failed to get image length from http response"); err = ESP_FAIL; diff --git a/docs/en/api-reference/system/esp_https_ota.rst b/docs/en/api-reference/system/esp_https_ota.rst index e3fcdd2025..d54f0300d2 100644 --- a/docs/en/api-reference/system/esp_https_ota.rst +++ b/docs/en/api-reference/system/esp_https_ota.rst @@ -47,6 +47,9 @@ This option is useful while fetching image from a service like AWS S3, where mbe Default value of mbedTLS Rx buffer size is set to 16 KB. By using ``partial_http_download`` with ``max_http_request_size`` of 4 KB, size of mbedTLS Rx buffer can be reduced to 4 KB. With this configuration, memory saving of around 12 KB is expected. +.. note:: + If the server uses chunked transfer encoding, partial downloads are not feasible because the total content length is not known in advance. + OTA Resumption -------------- diff --git a/docs/zh_CN/api-reference/system/esp_https_ota.rst b/docs/zh_CN/api-reference/system/esp_https_ota.rst index 669742c961..bcc64d4963 100644 --- a/docs/zh_CN/api-reference/system/esp_https_ota.rst +++ b/docs/zh_CN/api-reference/system/esp_https_ota.rst @@ -47,6 +47,9 @@ ESP HTTPS OTA 升级 mbedTLS Rx buffer 的默认大小为 16 KB,但如果将 ``partial_http_download`` 的 ``max_http_request_size`` 设置为 4 KB,便能将 mbedTLS Rx 的 buffer 减小到 4 KB。使用这一配置方式预计可以节省约 12 KB 内存。 +.. note:: + 如果服务器使用分块传输编码,则无法进行部分下载,因为无法预先获知总内容长度。 + OTA 恢复 --------