mgmt: hawkbit: resume firmware downloads

save download progress, to resume failed
firmware downloads.

Signed-off-by: Fin Maaß <f.maass@vogl-electronic.com>
This commit is contained in:
Fin Maaß 2024-04-25 16:43:20 +02:00 committed by Benjamin Cabé
parent d9eed167d0
commit 4cd44e7da2
3 changed files with 172 additions and 97 deletions

View file

@ -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

View file

@ -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.

View file

@ -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,14 +870,126 @@ int hawkbit_init(void)
return ret;
}
static void response_json_cb(struct http_response *rsp, enum http_final_call final_data,
struct hawkbit_context *hb_context)
{
size_t body_len;
int ret;
uint8_t *body_data = NULL, *rsp_tmp = NULL;
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 ((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;
}
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);
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;
}
} 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;
}
}
}
}
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;
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;
}
#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS
stream_flash_progress_save(&hb_context->flash_ctx.stream, "hawkbit/flash_progress");
#endif
}
hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
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;
}
}
static void response_cb(struct http_response *rsp, enum http_final_call final_data, void *userdata)
{
struct hawkbit_context *hb_context = userdata;
size_t body_len;
int ret, downloaded;
uint8_t *body_data = NULL, *rsp_tmp = NULL;
if (rsp->http_status_code != 200) {
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;
@ -884,103 +1002,14 @@ static void response_cb(struct http_response *rsp, enum http_final_call final_da
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 ((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;
}
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);
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;
}
}
}
response_json_cb(rsp, final_data, hb_context);
break;
case HAWKBIT_DOWNLOAD:
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;
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;
}
}
hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
downloaded =
hb_context->dl.downloaded_size * 100 / hb_context->dl.http_content_size;
if (downloaded > hb_context->dl.download_progress) {
hb_context->dl.download_progress = downloaded;
LOG_DBG("Downloaded: %d%% ", hb_context->dl.download_progress);
}
if (final_data == HTTP_DATA_FINAL) {
hb_context->final_data_received = true;
}
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;