Skip to content

Commit 4cd44e7

Browse files
maass-hamburgkartben
authored andcommitted
mgmt: hawkbit: resume firmware downloads
save download progress, to resume failed firmware downloads. Signed-off-by: Fin Maaß <[email protected]>
1 parent d9eed16 commit 4cd44e7

File tree

3 files changed

+166
-91
lines changed

3 files changed

+166
-91
lines changed

samples/subsys/mgmt/hawkbit/sample.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ tests:
3434
sample.net.hawkbit.set_settings_runtime:
3535
extra_configs:
3636
- CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME=y
37+
sample.net.hawkbit.save_progress:
38+
extra_configs:
39+
- CONFIG_HAWKBIT_SAVE_PROGRESS=y
40+
- CONFIG_STREAM_FLASH_PROGRESS=y

subsys/mgmt/hawkbit/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,17 @@ config HAWKBIT_EVENT_CALLBACKS
188188
help
189189
Be able to set event callbacks for hawkBit.
190190

191+
config HAWKBIT_SAVE_PROGRESS
192+
bool "Save the hawkBit update download progress"
193+
depends on STREAM_FLASH_PROGRESS
194+
help
195+
Regularly save progress of hawkBit update download operation.
196+
When enabled, the download progress is periodically saved to
197+
non-volatile storage. If a download is interrupted, it can be resumed from
198+
the last saved point rather than starting over, saving bandwidth and time.
199+
This is especially useful for large updates over unreliable networks or in
200+
resource-constrained environments.
201+
191202
module = HAWKBIT
192203
module-str = Log Level for hawkbit
193204
module-help = Enables logging for hawkBit code.

subsys/mgmt/hawkbit/hawkbit.c

Lines changed: 151 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ LOG_MODULE_REGISTER(hawkbit, CONFIG_HAWKBIT_LOG_LEVEL);
4444
#define SHA256_HASH_SIZE 32
4545
#define RESPONSE_BUFFER_SIZE 1100
4646
#define DDI_SECURITY_TOKEN_SIZE 32
47+
#define RANGE_HEADER_SIZE 50
4748
#define HAWKBIT_RECV_TIMEOUT (300 * MSEC_PER_SEC)
4849
#define HAWKBIT_SET_SERVER_TIMEOUT K_MSEC(300)
4950

@@ -115,8 +116,6 @@ static struct hawkbit_config {
115116
#endif /* CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG */
116117

117118
struct hawkbit_download {
118-
int download_status;
119-
int download_progress;
120119
size_t downloaded_size;
121120
size_t http_content_size;
122121
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
343342
return 0;
344343
}
345344
#endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */
345+
/* This is to omit the error message, as that is fetched in stream_flash_progress_load()
346+
* and we don't need to get it here.
347+
*/
348+
if (IS_ENABLED(CONFIG_HAWKBIT_SAVE_PROGRESS) &&
349+
settings_name_steq(name, "flash_progress", NULL)) {
350+
return 0;
351+
}
346352

347353
return -ENOENT;
348354
}
@@ -864,123 +870,146 @@ int hawkbit_init(void)
864870
return ret;
865871
}
866872

867-
static void response_cb(struct http_response *rsp, enum http_final_call final_data, void *userdata)
873+
static void response_json_cb(struct http_response *rsp, enum http_final_call final_data,
874+
struct hawkbit_context *hb_context)
868875
{
869-
struct hawkbit_context *hb_context = userdata;
870876
size_t body_len;
871-
int ret, downloaded;
877+
int ret;
872878
uint8_t *body_data = NULL, *rsp_tmp = NULL;
873879

874-
if (rsp->http_status_code != 200) {
875-
LOG_ERR("HTTP request denied: %d", rsp->http_status_code);
876-
if (rsp->http_status_code == 401 || rsp->http_status_code == 403) {
877-
hb_context->code_status = HAWKBIT_PERMISSION_ERROR;
878-
} else {
879-
hb_context->code_status = HAWKBIT_METADATA_ERROR;
880-
}
881-
return;
880+
if (hb_context->dl.http_content_size == 0) {
881+
hb_context->dl.http_content_size = rsp->content_length;
882882
}
883883

884-
switch (hb_context->type) {
885-
case HAWKBIT_PROBE:
886-
case HAWKBIT_PROBE_DEPLOYMENT_BASE:
887-
if (hb_context->dl.http_content_size == 0) {
888-
hb_context->dl.http_content_size = rsp->content_length;
889-
}
884+
if (rsp->body_found) {
885+
body_data = rsp->body_frag_start;
886+
body_len = rsp->body_frag_len;
890887

891-
if (rsp->body_found) {
892-
body_data = rsp->body_frag_start;
893-
body_len = rsp->body_frag_len;
894-
895-
if ((hb_context->dl.downloaded_size + body_len) >
896-
hb_context->response_data_size) {
897-
hb_context->response_data_size =
898-
hb_context->dl.downloaded_size + body_len;
899-
rsp_tmp = k_realloc(hb_context->response_data,
900-
hb_context->response_data_size);
901-
if (rsp_tmp == NULL) {
902-
LOG_ERR("Failed to realloc memory");
903-
hb_context->code_status = HAWKBIT_ALLOC_ERROR;
904-
break;
905-
}
906-
907-
hb_context->response_data = rsp_tmp;
888+
if ((hb_context->dl.downloaded_size + body_len) >
889+
hb_context->response_data_size) {
890+
hb_context->response_data_size =
891+
hb_context->dl.downloaded_size + body_len;
892+
rsp_tmp = k_realloc(hb_context->response_data,
893+
hb_context->response_data_size);
894+
if (rsp_tmp == NULL) {
895+
LOG_ERR("Failed to realloc memory");
896+
hb_context->code_status = HAWKBIT_ALLOC_ERROR;
897+
return;
908898
}
909-
strncpy(hb_context->response_data + hb_context->dl.downloaded_size,
910-
body_data, body_len);
911-
hb_context->dl.downloaded_size += body_len;
899+
900+
hb_context->response_data = rsp_tmp;
912901
}
902+
strncpy(hb_context->response_data + hb_context->dl.downloaded_size,
903+
body_data, body_len);
904+
hb_context->dl.downloaded_size += body_len;
905+
}
913906

914-
if (final_data == HTTP_DATA_FINAL) {
915-
if (hb_context->dl.http_content_size != hb_context->dl.downloaded_size) {
916-
LOG_ERR("HTTP response len mismatch, expected %d, got %d",
917-
hb_context->dl.http_content_size,
918-
hb_context->dl.downloaded_size);
907+
if (final_data == HTTP_DATA_FINAL) {
908+
if (hb_context->dl.http_content_size != hb_context->dl.downloaded_size) {
909+
LOG_ERR("HTTP response len mismatch, expected %d, got %d",
910+
hb_context->dl.http_content_size,
911+
hb_context->dl.downloaded_size);
912+
hb_context->code_status = HAWKBIT_METADATA_ERROR;
913+
return;
914+
}
915+
916+
hb_context->response_data[hb_context->dl.downloaded_size] = '\0';
917+
memset(&hb_context->results, 0, sizeof(hb_context->results));
918+
if (hb_context->type == HAWKBIT_PROBE) {
919+
ret = json_obj_parse(
920+
hb_context->response_data, hb_context->dl.downloaded_size,
921+
json_ctl_res_descr, ARRAY_SIZE(json_ctl_res_descr),
922+
&hb_context->results.base);
923+
if (ret < 0) {
924+
LOG_ERR("JSON parse error (%s): %d", "HAWKBIT_PROBE", ret);
919925
hb_context->code_status = HAWKBIT_METADATA_ERROR;
920-
break;
921926
}
922-
923-
hb_context->response_data[hb_context->dl.downloaded_size] = '\0';
924-
memset(&hb_context->results, 0, sizeof(hb_context->results));
925-
if (hb_context->type == HAWKBIT_PROBE) {
926-
ret = json_obj_parse(
927-
hb_context->response_data, hb_context->dl.downloaded_size,
928-
json_ctl_res_descr, ARRAY_SIZE(json_ctl_res_descr),
929-
&hb_context->results.base);
930-
if (ret < 0) {
931-
LOG_ERR("JSON parse error (%s): %d", "HAWKBIT_PROBE", ret);
932-
hb_context->code_status = HAWKBIT_METADATA_ERROR;
933-
}
934-
} else {
935-
ret = json_obj_parse(
936-
hb_context->response_data, hb_context->dl.downloaded_size,
937-
json_dep_res_descr, ARRAY_SIZE(json_dep_res_descr),
938-
&hb_context->results.dep);
939-
if (ret < 0) {
940-
LOG_ERR("JSON parse error (%s): %d", "deploymentBase", ret);
941-
hb_context->code_status = HAWKBIT_METADATA_ERROR;
942-
}
927+
} else {
928+
ret = json_obj_parse(
929+
hb_context->response_data, hb_context->dl.downloaded_size,
930+
json_dep_res_descr, ARRAY_SIZE(json_dep_res_descr),
931+
&hb_context->results.dep);
932+
if (ret < 0) {
933+
LOG_ERR("JSON parse error (%s): %d", "deploymentBase", ret);
934+
hb_context->code_status = HAWKBIT_METADATA_ERROR;
943935
}
944936
}
937+
}
938+
}
945939

946-
break;
947-
948-
case HAWKBIT_DOWNLOAD:
949-
if (hb_context->dl.http_content_size == 0) {
950-
hb_context->dl.http_content_size = rsp->content_length;
940+
static void response_download_cb(struct http_response *rsp, enum http_final_call final_data,
941+
struct hawkbit_context *hb_context)
942+
{
943+
size_t body_len;
944+
int ret, downloaded;
945+
uint8_t *body_data = NULL;
946+
static uint8_t download_progress;
947+
948+
if (hb_context->dl.http_content_size == 0) {
949+
hb_context->dl.http_content_size = rsp->content_length;
950+
download_progress = 0;
951+
if (IS_ENABLED(CONFIG_HAWKBIT_SAVE_PROGRESS) && rsp->http_status_code != 206) {
952+
hb_context->flash_ctx.stream.bytes_written = 0;
951953
}
954+
}
952955

953-
if (rsp->body_found) {
954-
body_data = rsp->body_frag_start;
955-
body_len = rsp->body_frag_len;
956+
if (rsp->body_found) {
957+
body_data = rsp->body_frag_start;
958+
body_len = rsp->body_frag_len;
956959

957-
ret = flash_img_buffered_write(&hb_context->flash_ctx, body_data, body_len,
958-
final_data == HTTP_DATA_FINAL);
959-
if (ret < 0) {
960-
LOG_ERR("Failed to write flash: %d", ret);
961-
hb_context->code_status = HAWKBIT_DOWNLOAD_ERROR;
962-
break;
963-
}
960+
ret = flash_img_buffered_write(&hb_context->flash_ctx, body_data, body_len,
961+
final_data == HTTP_DATA_FINAL);
962+
if (ret < 0) {
963+
LOG_ERR("Failed to write flash: %d", ret);
964+
hb_context->code_status = HAWKBIT_DOWNLOAD_ERROR;
965+
return;
964966
}
965967

966-
hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
968+
#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS
969+
stream_flash_progress_save(&hb_context->flash_ctx.stream, "hawkbit/flash_progress");
970+
#endif
971+
}
967972

968-
downloaded =
969-
hb_context->dl.downloaded_size * 100 / hb_context->dl.http_content_size;
973+
hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
970974

971-
if (downloaded > hb_context->dl.download_progress) {
972-
hb_context->dl.download_progress = downloaded;
973-
LOG_DBG("Downloaded: %d%% ", hb_context->dl.download_progress);
974-
}
975+
downloaded = hb_context->dl.downloaded_size * 100 / hb_context->dl.file_size;
976+
977+
if (downloaded != download_progress) {
978+
download_progress = downloaded;
979+
LOG_DBG("Downloaded: %u%% (%u / %u)", download_progress,
980+
hb_context->dl.downloaded_size, hb_context->dl.file_size);
981+
}
982+
983+
if (final_data == HTTP_DATA_FINAL) {
984+
hb_context->final_data_received = true;
985+
}
986+
}
975987

976-
if (final_data == HTTP_DATA_FINAL) {
977-
hb_context->final_data_received = true;
988+
static void response_cb(struct http_response *rsp, enum http_final_call final_data, void *userdata)
989+
{
990+
struct hawkbit_context *hb_context = userdata;
991+
992+
if (!IN_RANGE(rsp->http_status_code, 200, 299)) {
993+
LOG_ERR("HTTP request denied: %d", rsp->http_status_code);
994+
if (rsp->http_status_code == 401 || rsp->http_status_code == 403) {
995+
hb_context->code_status = HAWKBIT_PERMISSION_ERROR;
996+
} else {
997+
hb_context->code_status = HAWKBIT_METADATA_ERROR;
978998
}
999+
return;
1000+
}
9791001

1002+
switch (hb_context->type) {
1003+
case HAWKBIT_PROBE:
1004+
case HAWKBIT_PROBE_DEPLOYMENT_BASE:
1005+
response_json_cb(rsp, final_data, hb_context);
9801006
break;
9811007

982-
default:
1008+
case HAWKBIT_DOWNLOAD:
1009+
response_download_cb(rsp, final_data, hb_context);
1010+
break;
9831011

1012+
default:
9841013
break;
9851014
}
9861015
}
@@ -1055,6 +1084,13 @@ static bool send_request(struct hawkbit_context *hb_context, enum hawkbit_http_r
10551084
* Resource for software module (Deployment Base)
10561085
* GET: /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}
10571086
*/
1087+
1088+
http_req.method = HTTP_GET;
1089+
hb_context->dl.http_content_size = 0;
1090+
hb_context->dl.downloaded_size = 0;
1091+
1092+
break;
1093+
10581094
case HAWKBIT_DOWNLOAD:
10591095
/*
10601096
* Resource for software module (Deployment Base)
@@ -1063,7 +1099,23 @@ static bool send_request(struct hawkbit_context *hb_context, enum hawkbit_http_r
10631099
*/
10641100
http_req.method = HTTP_GET;
10651101
hb_context->dl.http_content_size = 0;
1102+
1103+
#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS
1104+
hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
1105+
if (IN_RANGE(hb_context->dl.downloaded_size, 1, hb_context->dl.file_size)) {
1106+
char header_range[RANGE_HEADER_SIZE] = {0};
1107+
1108+
snprintf(header_range, sizeof(header_range), "Range: bytes=%u-" HTTP_CRLF,
1109+
hb_context->dl.downloaded_size);
1110+
const char *const headers_range[] = {header_range, NULL};
1111+
1112+
http_req.optional_headers = (const char **)headers_range;
1113+
LOG_DBG("optional header: %s", header_range);
1114+
LOG_INF("Resuming download from %d bytes", hb_context->dl.downloaded_size);
1115+
}
1116+
#else
10661117
hb_context->dl.downloaded_size = 0;
1118+
#endif
10671119

10681120
break;
10691121

@@ -1459,6 +1511,10 @@ static void s_download(void *o)
14591511
*/
14601512
flash_area_ptr = s->hb_context.flash_ctx.flash_area;
14611513

1514+
#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS
1515+
stream_flash_progress_load(&s->hb_context.flash_ctx.stream, "hawkbit/flash_progress");
1516+
#endif
1517+
14621518
if (!send_request(&s->hb_context, HAWKBIT_DOWNLOAD, url_buffer, NULL)) {
14631519
LOG_ERR("Send request failed (%s)", "HAWKBIT_DOWNLOAD");
14641520
smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]);
@@ -1473,6 +1529,10 @@ static void s_download(void *o)
14731529
return;
14741530
}
14751531

1532+
#ifdef CONFIG_HAWKBIT_SAVE_PROGRESS
1533+
stream_flash_progress_clear(&s->hb_context.flash_ctx.stream, "hawkbit/flash_progress");
1534+
#endif
1535+
14761536
/* Verify the hash of the stored firmware */
14771537
fic.match = s->hb_context.dl.file_hash;
14781538
fic.clen = s->hb_context.dl.downloaded_size;

0 commit comments

Comments
 (0)