zephyr/subsys/mgmt/hawkbit/hawkbit.c
Yong Cong Sin 313c49ec21 subsys/mgmt/hawkbit: Check the hash of stored firmware
Previously, the hash of the firmware is checked while we are
downloading the firmware. This isn't ideal as the validity of
the firmware written into the flash is not verified and can be
corrupted. Furthermore, checking while downloading will have an
negative impact to the download speed as the CPU need to do
more work during the data transfer.

This PR removes the previous verify-hash-while-download
implementation and use the flash_img_check API instead.

Signed-off-by: Yong Cong Sin <yongcong.sin@gmail.com>
2022-03-31 10:28:22 +02:00

1324 lines
36 KiB
C

/*
* Copyright (c) 2016-2017 Linaro Limited
* Copyright (c) 2018 Open Source Foundries Limited
* Copyright (c) 2018 Foundries.io
* Copyright (c) 2020 Linumiz
* Copyright (c) 2021 G-Technologies Sdn. Bhd.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(hawkbit, CONFIG_HAWKBIT_LOG_LEVEL);
#include <stdio.h>
#include <zephyr.h>
#include <string.h>
#include <stdlib.h>
#include <fs/nvs.h>
#include <data/json.h>
#include <net/net_ip.h>
#include <net/socket.h>
#include <net/net_mgmt.h>
#include <sys/reboot.h>
#include <drivers/flash.h>
#include <net/http_client.h>
#include <net/dns_resolve.h>
#include <logging/log_ctrl.h>
#include <storage/flash_map.h>
#include "hawkbit_priv.h"
#include "hawkbit_device.h"
#include "mgmt/hawkbit.h"
#include "hawkbit_firmware.h"
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
#define CA_CERTIFICATE_TAG 1
#include <net/tls_credentials.h>
#endif
#define ADDRESS_ID 1
#define CANCEL_BASE_SIZE 50
#define RECV_BUFFER_SIZE 640
#define URL_BUFFER_SIZE 300
#define SHA256_HASH_SIZE 32
#define STATUS_BUFFER_SIZE 200
#define DOWNLOAD_HTTP_SIZE 200
#define DEPLOYMENT_BASE_SIZE 50
#define RESPONSE_BUFFER_SIZE 1100
#define HAWKBIT_RECV_TIMEOUT (300 * MSEC_PER_SEC)
#define SLOT1_SIZE FLASH_AREA_SIZE(image_1)
#define HTTP_HEADER_CONTENT_TYPE_JSON "application/json;charset=UTF-8"
#define STORAGE_NODE DT_NODE_BY_FIXED_PARTITION_LABEL(storage)
#define FLASH_NODE DT_MTD_FROM_FIXED_PARTITION(STORAGE_NODE)
#if ((CONFIG_HAWKBIT_POLL_INTERVAL > 1) && (CONFIG_HAWKBIT_POLL_INTERVAL < 43200))
static uint32_t poll_sleep = (CONFIG_HAWKBIT_POLL_INTERVAL * 60 * MSEC_PER_SEC);
#else
static uint32_t poll_sleep = (300 * MSEC_PER_SEC);
#endif
static struct nvs_fs fs;
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];
};
static struct hawkbit_context {
int sock;
int32_t action_id;
uint8_t *response_data;
int32_t json_action_id;
size_t url_buffer_size;
size_t status_buffer_size;
struct hawkbit_download dl;
struct http_request http_req;
struct flash_img_context flash_ctx;
uint8_t url_buffer[URL_BUFFER_SIZE];
uint8_t status_buffer[STATUS_BUFFER_SIZE];
uint8_t recv_buf_tcp[RECV_BUFFER_SIZE];
enum hawkbit_response code_status;
bool final_data_received;
} hb_context;
static union {
struct hawkbit_dep_res dep;
struct hawkbit_ctl_res base;
struct hawkbit_cancel cancel;
} hawkbit_results;
static struct k_work_delayable hawkbit_work_handle;
static struct k_sem probe_sem;
static const struct json_obj_descr json_href_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_href, href, JSON_TOK_STRING),
};
static const struct json_obj_descr json_status_result_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_status_result, finished,
JSON_TOK_STRING),
};
static const struct json_obj_descr json_status_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_status, execution, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_status, result,
json_status_result_descr),
};
static const struct json_obj_descr json_ctl_res_sleep_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_ctl_res_sleep, sleep,
JSON_TOK_STRING),
};
static const struct json_obj_descr json_ctl_res_polling_descr[] = {
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res_polling, polling,
json_ctl_res_sleep_descr),
};
static const struct json_obj_descr json_ctl_res_links_descr[] = {
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res_links, deploymentBase,
json_href_descr),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res_links, cancelAction,
json_href_descr),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res_links, configData,
json_href_descr),
};
static const struct json_obj_descr json_ctl_res_descr[] = {
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res, config,
json_ctl_res_polling_descr),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_ctl_res, _links,
json_ctl_res_links_descr),
};
static const struct json_obj_descr json_cfg_data_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_cfg_data, VIN, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_cfg_data, hwRevision,
JSON_TOK_STRING),
};
static const struct json_obj_descr json_cfg_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_cfg, mode, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_cfg, data, json_cfg_data_descr),
JSON_OBJ_DESCR_PRIM(struct hawkbit_cfg, id, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_cfg, time, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_cfg, status, json_status_descr),
};
static const struct json_obj_descr json_close_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_close, id, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_close, time, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_close, status, json_status_descr),
};
static const struct json_obj_descr json_dep_res_hashes_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_hashes, sha1,
JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_hashes, md5,
JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_hashes, sha256,
JSON_TOK_STRING),
};
static const struct json_obj_descr json_dep_res_links_descr[] = {
JSON_OBJ_DESCR_OBJECT_NAMED(struct hawkbit_dep_res_links,
"download-http", download_http,
json_href_descr),
JSON_OBJ_DESCR_OBJECT_NAMED(struct hawkbit_dep_res_links, "md5sum-http",
md5sum_http, json_href_descr),
};
static const struct json_obj_descr json_dep_res_arts_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_arts, filename,
JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_dep_res_arts, hashes,
json_dep_res_hashes_descr),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_arts, size, JSON_TOK_NUMBER),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_dep_res_arts, _links,
json_dep_res_links_descr),
};
static const struct json_obj_descr json_dep_res_chunk_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_chunk, part,
JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_chunk, version,
JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_chunk, name,
JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJ_ARRAY(struct hawkbit_dep_res_chunk, artifacts,
HAWKBIT_DEP_MAX_CHUNK_ARTS, num_artifacts,
json_dep_res_arts_descr,
ARRAY_SIZE(json_dep_res_arts_descr)),
};
static const struct json_obj_descr json_dep_res_deploy_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_deploy, download,
JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res_deploy, update,
JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJ_ARRAY(struct hawkbit_dep_res_deploy, chunks,
HAWKBIT_DEP_MAX_CHUNKS, num_chunks,
json_dep_res_chunk_descr,
ARRAY_SIZE(json_dep_res_chunk_descr)),
};
static const struct json_obj_descr json_dep_res_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_res, id, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_dep_res, deployment,
json_dep_res_deploy_descr),
};
static const struct json_obj_descr json_dep_fbk_descr[] = {
JSON_OBJ_DESCR_PRIM(struct hawkbit_dep_fbk, id, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct hawkbit_dep_fbk, status,
json_status_descr),
};
static bool start_http_client(void)
{
int ret = -1;
struct addrinfo *addr;
struct addrinfo hints;
int resolve_attempts = 10;
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
int protocol = IPPROTO_TLS_1_2;
#else
int protocol = IPPROTO_TCP;
#endif
if (IS_ENABLED(CONFIG_NET_IPV6)) {
hints.ai_family = AF_INET6;
} else if (IS_ENABLED(CONFIG_NET_IPV4)) {
hints.ai_family = AF_INET;
}
hints.ai_socktype = SOCK_STREAM;
while (resolve_attempts--) {
ret = getaddrinfo(CONFIG_HAWKBIT_SERVER, CONFIG_HAWKBIT_PORT,
&hints, &addr);
if (ret == 0) {
break;
}
k_sleep(K_MSEC(1));
}
if (ret != 0) {
LOG_ERR("Could not resolve dns: %d", ret);
return false;
}
hb_context.sock = socket(addr->ai_family, SOCK_STREAM, protocol);
if (hb_context.sock < 0) {
LOG_ERR("Failed to create TCP socket");
goto err;
}
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS)
sec_tag_t sec_tag_opt[] = {
CA_CERTIFICATE_TAG,
};
if (setsockopt(hb_context.sock, SOL_TLS, TLS_SEC_TAG_LIST, sec_tag_opt,
sizeof(sec_tag_opt)) < 0) {
LOG_ERR("Failed to set TLS_TAG option");
goto err_sock;
}
if (setsockopt(hb_context.sock, SOL_TLS, TLS_HOSTNAME,
CONFIG_HAWKBIT_SERVER,
sizeof(CONFIG_HAWKBIT_SERVER)) < 0) {
goto err_sock;
}
#endif
if (connect(hb_context.sock, addr->ai_addr, addr->ai_addrlen) < 0) {
LOG_ERR("Failed to connect to server");
goto err_sock;
}
freeaddrinfo(addr);
return true;
err_sock:
close(hb_context.sock);
err:
freeaddrinfo(addr);
return false;
}
static void cleanup_connection(void)
{
if (close(hb_context.sock) < 0) {
LOG_ERR("Could not close the socket");
}
}
static int hawkbit_time2sec(const char *s)
{
int sec;
/* Time: HH:MM:SS */
sec = strtol(s, NULL, 10) * (60 * 60);
sec += strtol(s + 3, NULL, 10) * 60;
sec += strtol(s + 6, NULL, 10);
if (sec < 0) {
return -1;
} else {
return sec;
}
}
static const char *hawkbit_status_finished(enum hawkbit_status_fini f)
{
switch (f) {
case HAWKBIT_STATUS_FINISHED_SUCCESS:
return "success";
case HAWKBIT_STATUS_FINISHED_FAILURE:
return "failure";
case HAWKBIT_STATUS_FINISHED_NONE:
return "none";
default:
LOG_ERR("%d is invalid", (int)f);
return NULL;
}
}
static const char *hawkbit_status_execution(enum hawkbit_status_exec e)
{
switch (e) {
case HAWKBIT_STATUS_EXEC_CLOSED:
return "closed";
case HAWKBIT_STATUS_EXEC_PROCEEDING:
return "proceeding";
case HAWKBIT_STATUS_EXEC_CANCELED:
return "canceled";
case HAWKBIT_STATUS_EXEC_SCHEDULED:
return "scheduled";
case HAWKBIT_STATUS_EXEC_REJECTED:
return "rejected";
case HAWKBIT_STATUS_EXEC_RESUMED:
return "resumed";
case HAWKBIT_STATUS_EXEC_NONE:
return "none";
default:
LOG_ERR("%d is invalid", (int)e);
return NULL;
}
}
static int hawkbit_device_acid_update(int32_t new_value)
{
int ret;
ret = nvs_write(&fs, ADDRESS_ID, &new_value, sizeof(new_value));
if (ret < 0) {
LOG_ERR("Failed to write device id: %d", ret);
return -EIO;
}
return 0;
}
/*
* Update sleep interval, based on results from hawkbit base polling
* resource
*/
static void hawkbit_update_sleep(struct hawkbit_ctl_res *hawkbit_res)
{
uint32_t sleep_time;
const char *sleep = hawkbit_res->config.polling.sleep;
if (strlen(sleep) != HAWKBIT_SLEEP_LENGTH) {
LOG_ERR("Invalid poll sleep: %s", log_strdup(sleep));
} else {
sleep_time = hawkbit_time2sec(sleep);
if (sleep_time > 0 &&
poll_sleep != (MSEC_PER_SEC * sleep_time)) {
LOG_DBG("New poll sleep %d seconds", sleep_time);
poll_sleep = sleep_time * MSEC_PER_SEC;
}
}
}
/*
* Find URL component for the device cancel operation and action id
*/
static int hawkbit_find_cancelAction_base(struct hawkbit_ctl_res *res,
char *cancel_base)
{
size_t len;
const char *href;
char *helper, *endptr;
href = res->_links.cancelAction.href;
if (!href) {
*cancel_base = '\0';
return 0;
}
helper = strstr(href, "cancelAction/");
if (!helper) {
/* A badly formatted cancel base is a server error */
LOG_ERR("Missing cancelBase/ in href %s", log_strdup(href));
return -EINVAL;
}
len = strlen(helper);
if (len > CANCEL_BASE_SIZE - 1) {
/* Lack of memory is an application error */
LOG_ERR("cancelBase %s is too big (len %zu, max %zu)", log_strdup(helper),
len, CANCEL_BASE_SIZE - 1);
return -ENOMEM;
}
strncpy(cancel_base, helper, CANCEL_BASE_SIZE);
helper = strtok(helper, "/");
if (helper == 0) {
return -EINVAL;
}
helper = strtok(NULL, "/");
if (helper == 0) {
return -EINVAL;
}
hb_context.action_id = strtol(helper, &endptr, 10);
if (hb_context.action_id <= 0) {
LOG_ERR("Invalid action ID: %d", hb_context.action_id);
return -EINVAL;
}
return 0;
}
/*
* Find URL component for the device's deployment operations
* resource
*/
static int hawkbit_find_deployment_base(struct hawkbit_ctl_res *res,
char *deployment_base)
{
const char *href;
const char *helper;
size_t len;
href = res->_links.deploymentBase.href;
if (!href) {
*deployment_base = '\0';
return 0;
}
helper = strstr(href, "deploymentBase/");
if (!helper) {
/* A badly formatted deployment base is a server error */
LOG_ERR("Missing deploymentBase/ in href %s", log_strdup(href));
return -EINVAL;
}
len = strlen(helper);
if (len > DEPLOYMENT_BASE_SIZE - 1) {
/* Lack of memory is an application error */
LOG_ERR("deploymentBase %s is too big (len %zu, max %zu)",
log_strdup(helper), len, DEPLOYMENT_BASE_SIZE - 1);
return -ENOMEM;
}
strncpy(deployment_base, helper, DEPLOYMENT_BASE_SIZE);
return 0;
}
/*
* Find URL component for this device's deployment operations
* resource.
*/
static int hawkbit_parse_deployment(struct hawkbit_dep_res *res,
int32_t *json_action_id,
char *download_http, int32_t *file_size)
{
int32_t size;
char *endptr;
const char *href;
const char *helper;
struct hawkbit_dep_res_chunk *chunk;
size_t len, num_chunks, num_artifacts;
struct hawkbit_dep_res_arts *artifact;
hb_context.action_id = strtol(res->id, &endptr, 10);
if (hb_context.action_id < 0) {
LOG_ERR("Negative action ID: %d", hb_context.action_id);
return -EINVAL;
}
*json_action_id = hb_context.action_id;
num_chunks = res->deployment.num_chunks;
if (num_chunks != 1) {
LOG_ERR("Expecting one chunk (got %d)", num_chunks);
return -ENOSPC;
}
chunk = &res->deployment.chunks[0];
if (strcmp("bApp", chunk->part)) {
LOG_ERR("Only part 'bApp' is supported; got %s", log_strdup(chunk->part));
return -EINVAL;
}
num_artifacts = chunk->num_artifacts;
if (num_artifacts != 1) {
LOG_ERR("Expecting one artifact (got %d)", num_artifacts);
return -EINVAL;
}
artifact = &chunk->artifacts[0];
if (hex2bin(artifact->hashes.sha256, SHA256_HASH_SIZE << 1,
hb_context.dl.file_hash, sizeof(hb_context.dl.file_hash)) !=
SHA256_HASH_SIZE) {
return -EINVAL;
}
size = artifact->size;
if (size > SLOT1_SIZE) {
LOG_ERR("Artifact file size too big (got %d, max is %d)", size,
SLOT1_SIZE);
return -ENOSPC;
}
/*
* Find the download-http href. We only support the DEFAULT
* tenant on the same hawkbit server.
*/
href = artifact->_links.download_http.href;
if (!href) {
LOG_ERR("Missing expected download-http href");
return -EINVAL;
}
helper = strstr(href, "/DEFAULT/controller/v1");
if (!helper) {
LOG_ERR("Unexpected download-http href format: %s", log_strdup(helper));
return -EINVAL;
}
len = strlen(helper);
if (len == 0) {
LOG_ERR("Empty download-http");
return -EINVAL;
} else if (len > DOWNLOAD_HTTP_SIZE - 1) {
LOG_ERR("download-http %s is too big (len: %zu, max: %zu)",
log_strdup(helper), len, DOWNLOAD_HTTP_SIZE - 1);
return -ENOMEM;
}
/* Success. */
strncpy(download_http, helper, DOWNLOAD_HTTP_SIZE);
*file_size = size;
return 0;
}
static void hawkbit_dump_base(struct hawkbit_ctl_res *r)
{
LOG_DBG("config.polling.sleep=%s", log_strdup(r->config.polling.sleep));
LOG_DBG("_links.deploymentBase.href=%s",
log_strdup(r->_links.deploymentBase.href));
LOG_DBG("_links.configData.href=%s",
log_strdup(r->_links.configData.href));
LOG_DBG("_links.cancelAction.href=%s",
log_strdup(r->_links.cancelAction.href));
}
static void hawkbit_dump_deployment(struct hawkbit_dep_res *d)
{
struct hawkbit_dep_res_chunk *c = &d->deployment.chunks[0];
struct hawkbit_dep_res_arts *a = &c->artifacts[0];
struct hawkbit_dep_res_links *l = &a->_links;
LOG_DBG("id=%s", log_strdup(d->id));
LOG_DBG("download=%s", log_strdup(d->deployment.download));
LOG_DBG("update=%s", log_strdup(d->deployment.update));
LOG_DBG("chunks[0].part=%s", log_strdup(c->part));
LOG_DBG("chunks[0].name=%s", log_strdup(c->name));
LOG_DBG("chunks[0].version=%s", log_strdup(c->version));
LOG_DBG("chunks[0].artifacts[0].filename=%s", log_strdup(a->filename));
LOG_DBG("chunks[0].artifacts[0].hashes.sha1=%s",
log_strdup(a->hashes.sha1));
LOG_DBG("chunks[0].artifacts[0].hashes.md5=%s",
log_strdup(a->hashes.md5));
LOG_DBG("chunks[0].artifacts[0].hashes.sha256=%s",
log_strdup(a->hashes.sha256));
LOG_DBG("chunks[0].size=%d", a->size);
LOG_DBG("download-http=%s", log_strdup(l->download_http.href));
LOG_DBG("md5sum =%s", log_strdup(l->md5sum_http.href));
}
int hawkbit_init(void)
{
bool image_ok;
int ret = 0, rc = 0;
struct flash_pages_info info;
int32_t action_id;
fs.flash_device = DEVICE_DT_GET(FLASH_NODE);
if (!device_is_ready(fs.flash_device)) {
LOG_ERR("Flash device not ready");
return -ENODEV;
}
fs.offset = FLASH_AREA_OFFSET(storage);
rc = flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info);
if (rc) {
LOG_ERR("Unable to get storage page info: %d", rc);
return -EIO;
}
fs.sector_size = info.size;
fs.sector_count = 3U;
rc = nvs_mount(&fs);
if (rc) {
LOG_ERR("Storage flash mount failed: %d", rc);
return rc;
}
rc = nvs_read(&fs, ADDRESS_ID, &action_id, sizeof(action_id));
LOG_DBG("Action id: current %d", action_id);
image_ok = boot_is_img_confirmed();
LOG_INF("Image is%s confirmed OK", image_ok ? "" : " not");
if (!image_ok) {
ret = boot_write_img_confirmed();
if (ret < 0) {
LOG_ERR("Couldn't confirm this image: %d", ret);
return ret;
}
LOG_DBG("Marked image as OK");
ret = boot_erase_img_bank(FLASH_AREA_ID(image_1));
if (ret) {
LOG_ERR("Failed to erase second slot: %d", ret);
return ret;
}
}
k_sem_init(&probe_sem, 1, 1);
return ret;
}
static int enum_for_http_req_string(char *userdata)
{
int i = 0;
char *name = http_request[i].http_req_str;
while (name) {
if (strcmp(name, userdata) == 0) {
return http_request[i].n;
}
name = http_request[++i].http_req_str;
}
return 0;
}
static void response_cb(struct http_response *rsp,
enum http_final_call final_data, void *userdata)
{
static size_t body_len;
int ret, type, downloaded;
uint8_t *body_data = NULL, *rsp_tmp = NULL;
static size_t response_buffer_size = RESPONSE_BUFFER_SIZE;
type = enum_for_http_req_string(userdata);
switch (type) {
case HAWKBIT_PROBE:
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) > response_buffer_size) {
response_buffer_size <<= 1;
rsp_tmp = realloc(hb_context.response_data,
response_buffer_size);
if (rsp_tmp == NULL) {
LOG_ERR("Failed to realloc memory");
hb_context.code_status =
HAWKBIT_METADATA_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';
ret = json_obj_parse(hb_context.response_data,
hb_context.dl.downloaded_size,
json_ctl_res_descr,
ARRAY_SIZE(json_ctl_res_descr),
&hawkbit_results.base);
if (ret < 0) {
LOG_ERR("JSON parse error (HAWKBIT_PROBE): %d", ret);
hb_context.code_status = HAWKBIT_METADATA_ERROR;
}
}
break;
case HAWKBIT_CLOSE:
case HAWKBIT_REPORT:
case HAWKBIT_CONFIG_DEVICE:
if (strcmp(rsp->http_status, "OK") < 0) {
LOG_ERR("Failed to cancel the update");
}
break;
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) > response_buffer_size) {
response_buffer_size <<= 1;
rsp_tmp = realloc(hb_context.response_data,
response_buffer_size);
if (rsp_tmp == NULL) {
LOG_ERR("Failed to realloc memory");
hb_context.code_status =
HAWKBIT_METADATA_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");
hb_context.code_status = HAWKBIT_METADATA_ERROR;
break;
}
hb_context.response_data[hb_context.dl.downloaded_size] = '\0';
ret = json_obj_parse(hb_context.response_data,
hb_context.dl.downloaded_size,
json_dep_res_descr,
ARRAY_SIZE(json_dep_res_descr),
&hawkbit_results.dep);
if (ret < 0) {
LOG_ERR("DeploymentBase JSON parse error: %d", 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;
}
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("Flash write error: %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("Download percentage: %d%% ",
hb_context.dl.download_progress);
}
if (final_data == HTTP_DATA_FINAL) {
hb_context.final_data_received = true;
}
break;
}
}
static bool send_request(enum http_method method,
enum hawkbit_http_request type,
enum hawkbit_status_fini finished,
enum hawkbit_status_exec execution)
{
int ret = 0;
struct hawkbit_cfg cfg;
struct hawkbit_close close;
struct hawkbit_dep_fbk feedback;
char acid[11];
const char *fini = hawkbit_status_finished(finished);
const char *exec = hawkbit_status_execution(execution);
char device_id[DEVICE_ID_HEX_MAX_SIZE] = { 0 };
#ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY
static const char * const headers[] = {
#ifdef CONFIG_HAWKBIT_DDI_GATEWAY_SECURITY
"Authorization: GatewayToken "CONFIG_HAWKBIT_DDI_SECURITY_TOKEN"\r\n",
#else
"Authorization: TargetToken "CONFIG_HAWKBIT_DDI_SECURITY_TOKEN"\r\n",
#endif /* CONFIG_HAWKBIT_DDI_GATEWAY_SECURITY */
NULL
};
#endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */
if (!hawkbit_get_device_identity(device_id, DEVICE_ID_HEX_MAX_SIZE)) {
hb_context.code_status = HAWKBIT_METADATA_ERROR;
}
memset(&hb_context.http_req, 0, sizeof(hb_context.http_req));
memset(&hb_context.recv_buf_tcp, 0, sizeof(hb_context.recv_buf_tcp));
hb_context.http_req.url = hb_context.url_buffer;
hb_context.http_req.method = method;
hb_context.http_req.host = CONFIG_HAWKBIT_SERVER;
hb_context.http_req.port = CONFIG_HAWKBIT_PORT;
hb_context.http_req.protocol = "HTTP/1.1";
hb_context.http_req.response = response_cb;
hb_context.http_req.recv_buf = hb_context.recv_buf_tcp;
hb_context.http_req.recv_buf_len = sizeof(hb_context.recv_buf_tcp);
#ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY
hb_context.http_req.header_fields = (const char **)headers;
#endif
hb_context.final_data_received = false;
switch (type) {
case HAWKBIT_PROBE:
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT, "HAWKBIT_PROBE");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_PROBE): %d", ret);
return false;
}
break;
case HAWKBIT_CONFIG_DEVICE:
memset(&cfg, 0, sizeof(cfg));
cfg.mode = "merge";
cfg.data.VIN = device_id;
cfg.data.hwRevision = "3";
cfg.id = "";
cfg.time = "";
cfg.status.execution = exec;
cfg.status.result.finished = fini;
ret = json_obj_encode_buf(json_cfg_descr,
ARRAY_SIZE(json_cfg_descr), &cfg,
hb_context.status_buffer,
hb_context.status_buffer_size - 1);
if (ret) {
LOG_ERR("Can't encode the JSON script (HAWKBIT_CONFIG_DEVICE): %d", ret);
return false;
}
hb_context.http_req.content_type_value =
HTTP_HEADER_CONTENT_TYPE_JSON;
hb_context.http_req.payload = hb_context.status_buffer;
hb_context.http_req.payload_len =
strlen(hb_context.status_buffer);
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT,
"HAWKBIT_CONFIG_DEVICE");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_CONFIG_DEVICE): %d", ret);
return false;
}
break;
case HAWKBIT_CLOSE:
memset(&close, 0, sizeof(close));
memset(&hb_context.status_buffer, 0,
sizeof(hb_context.status_buffer));
snprintk(acid, sizeof(acid), "%d", hb_context.action_id);
close.id = acid;
close.time = "";
close.status.execution = exec;
close.status.result.finished = fini;
ret = json_obj_encode_buf(json_close_descr,
ARRAY_SIZE(json_close_descr), &close,
hb_context.status_buffer,
hb_context.status_buffer_size - 1);
if (ret) {
LOG_ERR("Can't encode the JSON script (HAWKBIT_CLOSE): %d", ret);
return false;
}
hb_context.http_req.content_type_value =
HTTP_HEADER_CONTENT_TYPE_JSON;
hb_context.http_req.payload = hb_context.status_buffer;
hb_context.http_req.payload_len =
strlen(hb_context.status_buffer);
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT, "HAWKBIT_CLOSE");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_CLOSE): %d", ret);
return false;
}
break;
case HAWKBIT_PROBE_DEPLOYMENT_BASE:
hb_context.http_req.content_type_value = NULL;
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT,
"HAWKBIT_PROBE_DEPLOYMENT_BASE");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_PROBE_DEPLOYMENT_BASE): %d",
ret);
return false;
}
break;
case HAWKBIT_REPORT:
if (!fini || !exec) {
return -EINVAL;
}
LOG_INF("Reporting deployment feedback %s (%s) for action %d",
fini, exec, hb_context.json_action_id);
/* Build JSON */
memset(&feedback, 0, sizeof(feedback));
snprintk(acid, sizeof(acid), "%d", hb_context.json_action_id);
feedback.id = acid;
feedback.status.result.finished = fini;
feedback.status.execution = exec;
ret = json_obj_encode_buf(json_dep_fbk_descr,
ARRAY_SIZE(json_dep_fbk_descr),
&feedback, hb_context.status_buffer,
hb_context.status_buffer_size - 1);
if (ret) {
LOG_ERR("Can't encode the JSON script (HAWKBIT_REPORT): %d", ret);
return ret;
}
hb_context.http_req.content_type_value =
HTTP_HEADER_CONTENT_TYPE_JSON;
hb_context.http_req.payload = hb_context.status_buffer;
hb_context.http_req.payload_len =
strlen(hb_context.status_buffer);
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT, "HAWKBIT_REPORT");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_REPORT): %d", ret);
return false;
}
break;
case HAWKBIT_DOWNLOAD:
ret = http_client_req(hb_context.sock, &hb_context.http_req,
HAWKBIT_RECV_TIMEOUT, "HAWKBIT_DOWNLOAD");
if (ret < 0) {
LOG_ERR("Unable to send HTTP request (HAWKBIT_DOWNLOAD): %d", ret);
return false;
}
break;
}
return true;
}
enum hawkbit_response hawkbit_probe(void)
{
int ret;
int32_t action_id;
int32_t file_size = 0;
struct flash_img_check fic;
char device_id[DEVICE_ID_HEX_MAX_SIZE] = { 0 },
cancel_base[CANCEL_BASE_SIZE] = { 0 },
download_http[DOWNLOAD_HTTP_SIZE] = { 0 },
deployment_base[DEPLOYMENT_BASE_SIZE] = { 0 },
firmware_version[BOOT_IMG_VER_STRLEN_MAX] = { 0 };
if (k_sem_take(&probe_sem, K_NO_WAIT) != 0) {
return HAWKBIT_PROBE_IN_PROGRESS;
}
memset(&hb_context, 0, sizeof(hb_context));
hb_context.response_data = malloc(RESPONSE_BUFFER_SIZE);
if (!boot_is_img_confirmed()) {
LOG_ERR("The current image is not confirmed");
hb_context.code_status = HAWKBIT_UNCONFIRMED_IMAGE;
goto error;
}
if (!hawkbit_get_firmware_version(firmware_version,
BOOT_IMG_VER_STRLEN_MAX)) {
hb_context.code_status = HAWKBIT_METADATA_ERROR;
goto error;
}
if (!hawkbit_get_device_identity(device_id, DEVICE_ID_HEX_MAX_SIZE)) {
hb_context.code_status = HAWKBIT_METADATA_ERROR;
goto error;
}
if (!start_http_client()) {
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto error;
}
/*
* Query the hawkbit base polling resource.
*/
LOG_INF("Polling target data from Hawkbit");
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.dl.http_content_size = 0;
hb_context.dl.downloaded_size = 0;
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size, "%s/%s-%s",
HAWKBIT_JSON_URL, CONFIG_BOARD, device_id);
memset(&hawkbit_results.base, 0, sizeof(hawkbit_results.base));
if (!send_request(HTTP_GET, HAWKBIT_PROBE, HAWKBIT_STATUS_FINISHED_NONE,
HAWKBIT_STATUS_EXEC_NONE)) {
LOG_ERR("Send request failed (HAWKBIT_PROBE)");
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
if (hb_context.code_status == HAWKBIT_METADATA_ERROR) {
goto cleanup;
}
if (hawkbit_results.base.config.polling.sleep) {
/* Update the sleep time. */
hawkbit_update_sleep(&hawkbit_results.base);
}
hawkbit_dump_base(&hawkbit_results.base);
if (hawkbit_results.base._links.cancelAction.href) {
ret = hawkbit_find_cancelAction_base(&hawkbit_results.base,
cancel_base);
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.dl.http_content_size = 0;
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size,
"%s/%s-%s/%s/feedback", HAWKBIT_JSON_URL, CONFIG_BOARD,
device_id, cancel_base);
memset(&hawkbit_results.cancel, 0,
sizeof(hawkbit_results.cancel));
if (!send_request(HTTP_POST, HAWKBIT_CLOSE,
HAWKBIT_STATUS_FINISHED_SUCCESS,
HAWKBIT_STATUS_EXEC_CLOSED)) {
LOG_ERR("Send request failed (HAWKBIT_CLOSE)");
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
hb_context.code_status = HAWKBIT_CANCEL_UPDATE;
goto cleanup;
}
if (hawkbit_results.base._links.configData.href) {
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.dl.http_content_size = 0;
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size,
"%s/%s-%s/configData", HAWKBIT_JSON_URL, CONFIG_BOARD,
device_id);
if (!send_request(HTTP_PUT, HAWKBIT_CONFIG_DEVICE,
HAWKBIT_STATUS_FINISHED_SUCCESS,
HAWKBIT_STATUS_EXEC_CLOSED)) {
LOG_ERR("Send request failed (HAWKBIT_CONFIG_DEVICE)");
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
}
ret = hawkbit_find_deployment_base(&hawkbit_results.base,
deployment_base);
if (ret < 0) {
hb_context.code_status = HAWKBIT_METADATA_ERROR;
LOG_ERR("Unable to find URL for the device's deploymentBase: %d", ret);
goto cleanup;
}
if (strlen(deployment_base) == 0) {
hb_context.code_status = HAWKBIT_NO_UPDATE;
goto cleanup;
}
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.dl.http_content_size = 0;
hb_context.dl.downloaded_size = 0;
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size,
"%s/%s-%s/%s", HAWKBIT_JSON_URL, CONFIG_BOARD, device_id,
deployment_base);
memset(&hawkbit_results.dep, 0, sizeof(hawkbit_results.dep));
memset(hb_context.response_data, 0, RESPONSE_BUFFER_SIZE);
if (!send_request(HTTP_GET, HAWKBIT_PROBE_DEPLOYMENT_BASE,
HAWKBIT_STATUS_FINISHED_NONE,
HAWKBIT_STATUS_EXEC_NONE)) {
LOG_ERR("Send request failed (HAWKBIT_PROBE_DEPLOYMENT_BASE)");
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
if (hb_context.code_status == HAWKBIT_METADATA_ERROR) {
goto cleanup;
}
hawkbit_dump_deployment(&hawkbit_results.dep);
hb_context.dl.http_content_size = 0;
ret = hawkbit_parse_deployment(&hawkbit_results.dep,
&hb_context.json_action_id,
download_http, &file_size);
if (ret < 0) {
LOG_ERR("Unable to parse deployment base: %d", ret);
goto cleanup;
}
nvs_read(&fs, ADDRESS_ID, &action_id, sizeof(action_id));
if (action_id == (int32_t)hb_context.json_action_id) {
LOG_INF("Preventing repeated attempt to install %d",
hb_context.json_action_id);
hb_context.dl.http_content_size = 0;
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size,
"%s/%s-%s/deploymentBase/%d/feedback",
HAWKBIT_JSON_URL, CONFIG_BOARD, device_id,
hb_context.json_action_id);
if (!send_request(HTTP_POST, HAWKBIT_REPORT,
HAWKBIT_STATUS_FINISHED_SUCCESS,
HAWKBIT_STATUS_EXEC_CLOSED)) {
LOG_ERR("Send request failed (HAWKBIT_REPORT)");
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
hb_context.code_status = HAWKBIT_OK;
goto cleanup;
}
LOG_INF("Ready to install update");
hb_context.dl.http_content_size = 0;
memset(hb_context.url_buffer, 0, sizeof(hb_context.url_buffer));
hb_context.url_buffer_size = URL_BUFFER_SIZE;
snprintk(hb_context.url_buffer, hb_context.url_buffer_size, "%s",
download_http);
flash_img_init(&hb_context.flash_ctx);
ret = (int)send_request(HTTP_GET, HAWKBIT_DOWNLOAD,
HAWKBIT_STATUS_FINISHED_NONE,
HAWKBIT_STATUS_EXEC_NONE);
if (!ret) {
LOG_ERR("Send request failed (HAWKBIT_DOWNLOAD): %d", ret);
hb_context.code_status = HAWKBIT_NETWORKING_ERROR;
goto cleanup;
}
if (hb_context.code_status == HAWKBIT_DOWNLOAD_ERROR) {
goto cleanup;
}
/* Check if download finished */
if (!hb_context.final_data_received) {
LOG_ERR("Download is not complete");
hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR;
goto cleanup;
}
/* Verify the hash of the stored firmware */
fic.match = hb_context.dl.file_hash;
fic.clen = hb_context.dl.downloaded_size;
if (flash_img_check(&hb_context.flash_ctx, &fic, FLASH_AREA_ID(image_1))) {
LOG_ERR("Firmware - flash validation has failed");
hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR;
goto cleanup;
}
/* Request mcuboot to upgrade */
if (boot_request_upgrade(BOOT_UPGRADE_TEST)) {
LOG_ERR("Failed to mark the image in slot 1 as pending");
hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR;
goto cleanup;
}
/* If everything is successful */
hb_context.code_status = HAWKBIT_UPDATE_INSTALLED;
hawkbit_device_acid_update(hb_context.json_action_id);
hb_context.dl.http_content_size = 0;
cleanup:
cleanup_connection();
error:
free(hb_context.response_data);
k_sem_give(&probe_sem);
return hb_context.code_status;
}
static void autohandler(struct k_work *work)
{
switch (hawkbit_probe()) {
case HAWKBIT_UNCONFIRMED_IMAGE:
LOG_ERR("Image is unconfirmed");
LOG_ERR("Rebooting to previous confirmed image");
LOG_ERR("If this image is flashed using a hardware tool");
LOG_ERR("Make sure that it is a confirmed image");
k_sleep(K_SECONDS(1));
sys_reboot(SYS_REBOOT_WARM);
break;
case HAWKBIT_NO_UPDATE:
LOG_INF("No update found");
break;
case HAWKBIT_CANCEL_UPDATE:
LOG_INF("Hawkbit update cancelled from server");
break;
case HAWKBIT_OK:
LOG_INF("Image is already updated");
break;
case HAWKBIT_UPDATE_INSTALLED:
LOG_INF("Update installed, please reboot");
break;
case HAWKBIT_DOWNLOAD_ERROR:
LOG_INF("Update failed");
break;
case HAWKBIT_NETWORKING_ERROR:
LOG_INF("Network error");
break;
case HAWKBIT_METADATA_ERROR:
LOG_INF("Metadata error");
break;
case HAWKBIT_PROBE_IN_PROGRESS:
LOG_INF("Hawkbit is already running");
break;
}
k_work_reschedule(&hawkbit_work_handle, K_MSEC(poll_sleep));
}
void hawkbit_autohandler(void)
{
k_work_init_delayable(&hawkbit_work_handle, autohandler);
k_work_reschedule(&hawkbit_work_handle, K_NO_WAIT);
}