Skip to content

Commit

Permalink
mgmt: hawkbit: resume firmware downloads
Browse files Browse the repository at this point in the history
save download progress, to resume failed
firmware downloads.

Signed-off-by: Fin Maaß <f.maass@vogl-electronic.com>
  • Loading branch information
maass-hamburg committed May 2, 2024
1 parent 3b58b69 commit 6c5801b
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 90 deletions.
8 changes: 8 additions & 0 deletions subsys/mgmt/hawkbit/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ menuconfig HAWKBIT
select MPU_ALLOW_FLASH_WRITE
select IMG_ENABLE_IMAGE_CHECK
select IMG_ERASE_PROGRESSIVELY
imply STREAM_FLASH_PROGRESS
help
hawkBit is a domain independent back-end framework for polling out
software updates to constrained edge devices as well as more powerful
Expand Down Expand Up @@ -120,6 +121,13 @@ config HAWKBIT_DEVICE_ID_MAX_LENGTH
help
Maximum length of the device id.

config HAWKBIT_SAVE_PROGRESS
bool "Save the hawkBit update download progress"
depends on STREAM_FLASH_PROGRESS
default y
help
Save the hawkBit update download progress.

module = HAWKBIT
module-str = Log Level for hawkbit
module-help = Enables logging for hawkBit code.
Expand Down
235 changes: 145 additions & 90 deletions subsys/mgmt/hawkbit/hawkbit.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,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)

Expand Down Expand Up @@ -105,8 +106,6 @@ static struct hawkbit_config {
#endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */

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];
Expand Down Expand Up @@ -795,118 +794,143 @@ 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_buffer_size) {
hb_context->response_buffer_size =
hb_context->dl.downloaded_size + body_len;
rsp_tmp = realloc(hb_context->response_data,
hb_context->response_buffer_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_buffer_size) {
hb_context->response_buffer_size =
hb_context->dl.downloaded_size + body_len;
rsp_tmp = realloc(hb_context->response_data,
hb_context->response_buffer_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);
hb_context->code_status = HAWKBIT_METADATA_ERROR;
return;
}

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->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);
IF_ENABLED(CONFIG_HAWKBIT_SAVE_PROGRESS,
(stream_flash_progress_save(&hb_context->flash_ctx.stream,
"hawkbit_download");))
}

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 (final_data == HTTP_DATA_FINAL) {
hb_context->final_data_received = true;
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;

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;

case HAWKBIT_DOWNLOAD:
response_download_cb(rsp, final_data, hb_context);

break;

Expand Down Expand Up @@ -986,6 +1010,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)
Expand All @@ -994,7 +1025,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;

Expand Down Expand Up @@ -1376,6 +1423,10 @@ static void s_download(void *o)

flash_img_init(&s->hb_context.flash_ctx);

IF_ENABLED(
CONFIG_HAWKBIT_SAVE_PROGRESS,
(stream_flash_progress_load(&s->hb_context.flash_ctx.stream, "hawkbit_download");))

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]);
Expand All @@ -1390,6 +1441,10 @@ static void s_download(void *o)
return;
}

IF_ENABLED(
CONFIG_HAWKBIT_SAVE_PROGRESS,
(stream_flash_progress_clear(&s->hb_context.flash_ctx.stream, "hawkbit_download");))

/* Verify the hash of the stored firmware */
struct flash_img_check fic = {0};

Expand Down

0 comments on commit 6c5801b

Please sign in to comment.