| /* |
| * 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 <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(hawkbit, CONFIG_HAWKBIT_LOG_LEVEL); |
| |
| #include <stdio.h> |
| #include <zephyr/zephyr.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <zephyr/fs/nvs.h> |
| #include <zephyr/data/json.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/net/net_mgmt.h> |
| #include <zephyr/sys/reboot.h> |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/net/http_client.h> |
| #include <zephyr/net/dns_resolve.h> |
| #include <zephyr/logging/log_ctrl.h> |
| #include <zephyr/storage/flash_map.h> |
| |
| #include "hawkbit_priv.h" |
| #include "hawkbit_device.h" |
| #include <zephyr/mgmt/hawkbit.h> |
| #include "hawkbit_firmware.h" |
| |
| #if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
| #define CA_CERTIFICATE_TAG 1 |
| #include <zephyr/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", 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", 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)", 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", 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)", 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", 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", 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)", 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", r->config.polling.sleep); |
| LOG_DBG("_links.deploymentBase.href=%s", r->_links.deploymentBase.href); |
| LOG_DBG("_links.configData.href=%s", r->_links.configData.href); |
| LOG_DBG("_links.cancelAction.href=%s", 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", d->id); |
| LOG_DBG("download=%s", d->deployment.download); |
| LOG_DBG("update=%s", d->deployment.update); |
| LOG_DBG("chunks[0].part=%s", c->part); |
| LOG_DBG("chunks[0].name=%s", c->name); |
| LOG_DBG("chunks[0].version=%s", c->version); |
| LOG_DBG("chunks[0].artifacts[0].filename=%s", a->filename); |
| LOG_DBG("chunks[0].artifacts[0].hashes.sha1=%s", a->hashes.sha1); |
| LOG_DBG("chunks[0].artifacts[0].hashes.md5=%s", a->hashes.md5); |
| LOG_DBG("chunks[0].artifacts[0].hashes.sha256=%s", a->hashes.sha256); |
| LOG_DBG("chunks[0].size=%d", a->size); |
| LOG_DBG("download-http=%s", l->download_http.href); |
| LOG_DBG("md5sum =%s", 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); |
| } |