Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgmt: hawkbit: resume download of firmware #71954

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions samples/subsys/mgmt/hawkbit/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 15 additions & 0 deletions subsys/mgmt/hawkbit/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ 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
Save the hawkBit update download progress.

config HAWKBIT_SAVE_PROGRESS_INTERVAL
int "Save the hawkBit update download progress interval"
default 0
range 0 100
depends on HAWKBIT_SAVE_PROGRESS
help
Set the interval (in percent) that the hawkBit update download progress will be saved.
0 means that the progress will be saved every time a new chunk is downloaded.

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

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -864,123 +870,155 @@ 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);
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) {
return;
}

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;
}
}
body_data = rsp->body_frag_start;
body_len = rsp->body_frag_len;

hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx);
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;
}

#if defined CONFIG_HAWKBIT_SAVE_PROGRESS && IS_EQ(CONFIG_HAWKBIT_SAVE_PROGRESS_INTERVAL, 0)
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) {
#if defined CONFIG_HAWKBIT_SAVE_PROGRESS && !IS_EQ(CONFIG_HAWKBIT_SAVE_PROGRESS_INTERVAL, 0)
if ((downloaded / CONFIG_HAWKBIT_SAVE_PROGRESS_INTERVAL) >
(download_progress / CONFIG_HAWKBIT_SAVE_PROGRESS_INTERVAL)) {
stream_flash_progress_save(&hb_context->flash_ctx.stream,
"hawkbit/flash_progress");
}
#endif
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;
}
}
Expand Down Expand Up @@ -1055,6 +1093,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 @@ -1063,7 +1108,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 @@ -1459,6 +1520,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]);
Expand All @@ -1473,6 +1538,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;
Expand Down
Loading