From 4cd44e7da215ea8f3d0a042551a46fa5969bf999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fin=20Maa=C3=9F?= Date: Thu, 25 Apr 2024 16:43:20 +0200 Subject: [PATCH] mgmt: hawkbit: resume firmware downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit save download progress, to resume failed firmware downloads. Signed-off-by: Fin Maaß --- samples/subsys/mgmt/hawkbit/sample.yaml | 4 + subsys/mgmt/hawkbit/Kconfig | 11 ++ subsys/mgmt/hawkbit/hawkbit.c | 242 +++++++++++++++--------- 3 files changed, 166 insertions(+), 91 deletions(-) diff --git a/samples/subsys/mgmt/hawkbit/sample.yaml b/samples/subsys/mgmt/hawkbit/sample.yaml index 4a7bf0db6c7..6104df0035a 100644 --- a/samples/subsys/mgmt/hawkbit/sample.yaml +++ b/samples/subsys/mgmt/hawkbit/sample.yaml @@ -34,3 +34,7 @@ tests: sample.net.hawkbit.set_settings_runtime: extra_configs: - CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME=y + sample.net.hawkbit.save_progress: + extra_configs: + - CONFIG_HAWKBIT_SAVE_PROGRESS=y + - CONFIG_STREAM_FLASH_PROGRESS=y diff --git a/subsys/mgmt/hawkbit/Kconfig b/subsys/mgmt/hawkbit/Kconfig index 8004fac4c1c..984c31602a6 100644 --- a/subsys/mgmt/hawkbit/Kconfig +++ b/subsys/mgmt/hawkbit/Kconfig @@ -188,6 +188,17 @@ config HAWKBIT_EVENT_CALLBACKS help Be able to set event callbacks for hawkBit. +config HAWKBIT_SAVE_PROGRESS + bool "Save the hawkBit update download progress" + depends on STREAM_FLASH_PROGRESS + help + Regularly save progress of hawkBit update download operation. + When enabled, the download progress is periodically saved to + non-volatile storage. If a download is interrupted, it can be resumed from + the last saved point rather than starting over, saving bandwidth and time. + This is especially useful for large updates over unreliable networks or in + resource-constrained environments. + module = HAWKBIT module-str = Log Level for hawkbit module-help = Enables logging for hawkBit code. diff --git a/subsys/mgmt/hawkbit/hawkbit.c b/subsys/mgmt/hawkbit/hawkbit.c index 2e833a2b078..d6ca01cb7e6 100644 --- a/subsys/mgmt/hawkbit/hawkbit.c +++ b/subsys/mgmt/hawkbit/hawkbit.c @@ -44,6 +44,7 @@ LOG_MODULE_REGISTER(hawkbit, CONFIG_HAWKBIT_LOG_LEVEL); #define SHA256_HASH_SIZE 32 #define RESPONSE_BUFFER_SIZE 1100 #define DDI_SECURITY_TOKEN_SIZE 32 +#define RANGE_HEADER_SIZE 50 #define HAWKBIT_RECV_TIMEOUT (300 * MSEC_PER_SEC) #define HAWKBIT_SET_SERVER_TIMEOUT K_MSEC(300) @@ -115,8 +116,6 @@ static struct hawkbit_config { #endif /* CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG */ struct hawkbit_download { - int download_status; - int download_progress; size_t downloaded_size; size_t http_content_size; uint8_t file_hash[SHA256_HASH_SIZE]; @@ -343,6 +342,13 @@ static int hawkbit_settings_set(const char *name, size_t len, settings_read_cb r return 0; } #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ + /* This is to omit the error message, as that is fetched in stream_flash_progress_load() + * and we don't need to get it here. + */ + if (IS_ENABLED(CONFIG_HAWKBIT_SAVE_PROGRESS) && + settings_name_steq(name, "flash_progress", NULL)) { + return 0; + } return -ENOENT; } @@ -864,123 +870,146 @@ int hawkbit_init(void) return ret; } -static void response_cb(struct http_response *rsp, enum http_final_call final_data, void *userdata) +static void response_json_cb(struct http_response *rsp, enum http_final_call final_data, + struct hawkbit_context *hb_context) { - struct hawkbit_context *hb_context = userdata; size_t body_len; - int ret, downloaded; + int ret; uint8_t *body_data = NULL, *rsp_tmp = NULL; - if (rsp->http_status_code != 200) { - LOG_ERR("HTTP request denied: %d", rsp->http_status_code); - if (rsp->http_status_code == 401 || rsp->http_status_code == 403) { - hb_context->code_status = HAWKBIT_PERMISSION_ERROR; - } else { - hb_context->code_status = HAWKBIT_METADATA_ERROR; - } - return; + if (hb_context->dl.http_content_size == 0) { + hb_context->dl.http_content_size = rsp->content_length; } - switch (hb_context->type) { - case HAWKBIT_PROBE: - case HAWKBIT_PROBE_DEPLOYMENT_BASE: - if (hb_context->dl.http_content_size == 0) { - hb_context->dl.http_content_size = rsp->content_length; - } + if (rsp->body_found) { + body_data = rsp->body_frag_start; + body_len = rsp->body_frag_len; - if (rsp->body_found) { - body_data = rsp->body_frag_start; - body_len = rsp->body_frag_len; - - if ((hb_context->dl.downloaded_size + body_len) > - hb_context->response_data_size) { - hb_context->response_data_size = - hb_context->dl.downloaded_size + body_len; - rsp_tmp = k_realloc(hb_context->response_data, - hb_context->response_data_size); - if (rsp_tmp == NULL) { - LOG_ERR("Failed to realloc memory"); - hb_context->code_status = HAWKBIT_ALLOC_ERROR; - break; - } - - hb_context->response_data = rsp_tmp; + if ((hb_context->dl.downloaded_size + body_len) > + hb_context->response_data_size) { + hb_context->response_data_size = + hb_context->dl.downloaded_size + body_len; + rsp_tmp = k_realloc(hb_context->response_data, + hb_context->response_data_size); + if (rsp_tmp == NULL) { + LOG_ERR("Failed to realloc memory"); + hb_context->code_status = HAWKBIT_ALLOC_ERROR; + return; } - strncpy(hb_context->response_data + hb_context->dl.downloaded_size, - body_data, body_len); - hb_context->dl.downloaded_size += body_len; + + hb_context->response_data = rsp_tmp; } + strncpy(hb_context->response_data + hb_context->dl.downloaded_size, + body_data, body_len); + hb_context->dl.downloaded_size += body_len; + } - if (final_data == HTTP_DATA_FINAL) { - if (hb_context->dl.http_content_size != hb_context->dl.downloaded_size) { - LOG_ERR("HTTP response len mismatch, expected %d, got %d", - hb_context->dl.http_content_size, - hb_context->dl.downloaded_size); + if (final_data == HTTP_DATA_FINAL) { + if (hb_context->dl.http_content_size != hb_context->dl.downloaded_size) { + LOG_ERR("HTTP response len mismatch, expected %d, got %d", + hb_context->dl.http_content_size, + hb_context->dl.downloaded_size); + hb_context->code_status = HAWKBIT_METADATA_ERROR; + return; + } + + hb_context->response_data[hb_context->dl.downloaded_size] = '\0'; + memset(&hb_context->results, 0, sizeof(hb_context->results)); + if (hb_context->type == HAWKBIT_PROBE) { + ret = json_obj_parse( + hb_context->response_data, hb_context->dl.downloaded_size, + json_ctl_res_descr, ARRAY_SIZE(json_ctl_res_descr), + &hb_context->results.base); + if (ret < 0) { + LOG_ERR("JSON parse error (%s): %d", "HAWKBIT_PROBE", ret); hb_context->code_status = HAWKBIT_METADATA_ERROR; - break; } - - hb_context->response_data[hb_context->dl.downloaded_size] = '\0'; - memset(&hb_context->results, 0, sizeof(hb_context->results)); - if (hb_context->type == HAWKBIT_PROBE) { - ret = json_obj_parse( - hb_context->response_data, hb_context->dl.downloaded_size, - json_ctl_res_descr, ARRAY_SIZE(json_ctl_res_descr), - &hb_context->results.base); - if (ret < 0) { - LOG_ERR("JSON parse error (%s): %d", "HAWKBIT_PROBE", ret); - hb_context->code_status = HAWKBIT_METADATA_ERROR; - } - } else { - ret = json_obj_parse( - hb_context->response_data, hb_context->dl.downloaded_size, - json_dep_res_descr, ARRAY_SIZE(json_dep_res_descr), - &hb_context->results.dep); - if (ret < 0) { - LOG_ERR("JSON parse error (%s): %d", "deploymentBase", ret); - hb_context->code_status = HAWKBIT_METADATA_ERROR; - } + } else { + ret = json_obj_parse( + hb_context->response_data, hb_context->dl.downloaded_size, + json_dep_res_descr, ARRAY_SIZE(json_dep_res_descr), + &hb_context->results.dep); + if (ret < 0) { + LOG_ERR("JSON parse error (%s): %d", "deploymentBase", ret); + hb_context->code_status = HAWKBIT_METADATA_ERROR; } } + } +} - break; - - case HAWKBIT_DOWNLOAD: - if (hb_context->dl.http_content_size == 0) { - hb_context->dl.http_content_size = rsp->content_length; +static void response_download_cb(struct http_response *rsp, enum http_final_call final_data, + struct hawkbit_context *hb_context) +{ + size_t body_len; + int ret, downloaded; + uint8_t *body_data = NULL; + static uint8_t download_progress; + + if (hb_context->dl.http_content_size == 0) { + hb_context->dl.http_content_size = rsp->content_length; + download_progress = 0; + if (IS_ENABLED(CONFIG_HAWKBIT_SAVE_PROGRESS) && rsp->http_status_code != 206) { + hb_context->flash_ctx.stream.bytes_written = 0; } + } - if (rsp->body_found) { - body_data = rsp->body_frag_start; - body_len = rsp->body_frag_len; + if (rsp->body_found) { + body_data = rsp->body_frag_start; + body_len = rsp->body_frag_len; - ret = flash_img_buffered_write(&hb_context->flash_ctx, body_data, body_len, - final_data == HTTP_DATA_FINAL); - if (ret < 0) { - LOG_ERR("Failed to write flash: %d", ret); - hb_context->code_status = HAWKBIT_DOWNLOAD_ERROR; - break; - } + ret = flash_img_buffered_write(&hb_context->flash_ctx, body_data, body_len, + final_data == HTTP_DATA_FINAL); + if (ret < 0) { + LOG_ERR("Failed to write flash: %d", ret); + hb_context->code_status = HAWKBIT_DOWNLOAD_ERROR; + return; } - hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx); +#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS + stream_flash_progress_save(&hb_context->flash_ctx.stream, "hawkbit/flash_progress"); +#endif + } - downloaded = - hb_context->dl.downloaded_size * 100 / hb_context->dl.http_content_size; + hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx); - if (downloaded > hb_context->dl.download_progress) { - hb_context->dl.download_progress = downloaded; - LOG_DBG("Downloaded: %d%% ", hb_context->dl.download_progress); - } + downloaded = hb_context->dl.downloaded_size * 100 / hb_context->dl.file_size; + + if (downloaded != download_progress) { + download_progress = downloaded; + LOG_DBG("Downloaded: %u%% (%u / %u)", download_progress, + hb_context->dl.downloaded_size, hb_context->dl.file_size); + } + + if (final_data == HTTP_DATA_FINAL) { + hb_context->final_data_received = true; + } +} - if (final_data == HTTP_DATA_FINAL) { - hb_context->final_data_received = true; +static void response_cb(struct http_response *rsp, enum http_final_call final_data, void *userdata) +{ + struct hawkbit_context *hb_context = userdata; + + if (!IN_RANGE(rsp->http_status_code, 200, 299)) { + LOG_ERR("HTTP request denied: %d", rsp->http_status_code); + if (rsp->http_status_code == 401 || rsp->http_status_code == 403) { + hb_context->code_status = HAWKBIT_PERMISSION_ERROR; + } else { + hb_context->code_status = HAWKBIT_METADATA_ERROR; } + return; + } + switch (hb_context->type) { + case HAWKBIT_PROBE: + case HAWKBIT_PROBE_DEPLOYMENT_BASE: + response_json_cb(rsp, final_data, hb_context); break; - default: + case HAWKBIT_DOWNLOAD: + response_download_cb(rsp, final_data, hb_context); + break; + default: break; } } @@ -1055,6 +1084,13 @@ static bool send_request(struct hawkbit_context *hb_context, enum hawkbit_http_r * Resource for software module (Deployment Base) * GET: /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId} */ + + http_req.method = HTTP_GET; + hb_context->dl.http_content_size = 0; + hb_context->dl.downloaded_size = 0; + + break; + case HAWKBIT_DOWNLOAD: /* * Resource for software module (Deployment Base) @@ -1063,7 +1099,23 @@ static bool send_request(struct hawkbit_context *hb_context, enum hawkbit_http_r */ http_req.method = HTTP_GET; hb_context->dl.http_content_size = 0; + +#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS + hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx); + if (IN_RANGE(hb_context->dl.downloaded_size, 1, hb_context->dl.file_size)) { + char header_range[RANGE_HEADER_SIZE] = {0}; + + snprintf(header_range, sizeof(header_range), "Range: bytes=%u-" HTTP_CRLF, + hb_context->dl.downloaded_size); + const char *const headers_range[] = {header_range, NULL}; + + http_req.optional_headers = (const char **)headers_range; + LOG_DBG("optional header: %s", header_range); + LOG_INF("Resuming download from %d bytes", hb_context->dl.downloaded_size); + } +#else hb_context->dl.downloaded_size = 0; +#endif break; @@ -1459,6 +1511,10 @@ static void s_download(void *o) */ flash_area_ptr = s->hb_context.flash_ctx.flash_area; +#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS + stream_flash_progress_load(&s->hb_context.flash_ctx.stream, "hawkbit/flash_progress"); +#endif + if (!send_request(&s->hb_context, HAWKBIT_DOWNLOAD, url_buffer, NULL)) { LOG_ERR("Send request failed (%s)", "HAWKBIT_DOWNLOAD"); smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); @@ -1473,6 +1529,10 @@ static void s_download(void *o) return; } +#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS + stream_flash_progress_clear(&s->hb_context.flash_ctx.stream, "hawkbit/flash_progress"); +#endif + /* Verify the hash of the stored firmware */ fic.match = s->hb_context.dl.file_hash; fic.clen = s->hb_context.dl.downloaded_size;