| /* |
| * 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. |
| * Copyright (c) 2024 Vogl Electronic GmbH |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <zephyr/data/json.h> |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/logging/log_ctrl.h> |
| #include <zephyr/mgmt/hawkbit/hawkbit.h> |
| #include <zephyr/mgmt/hawkbit/config.h> |
| #include <zephyr/mgmt/hawkbit/event.h> |
| #include <zephyr/net/http/client.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/net_mgmt.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/settings/settings.h> |
| #include <zephyr/smf.h> |
| #include <zephyr/storage/flash_map.h> |
| #include <zephyr/sys/reboot.h> |
| |
| #include <bootutil/bootutil_public.h> |
| |
| #include "hawkbit_device.h" |
| #include "hawkbit_firmware.h" |
| #include "hawkbit_priv.h" |
| |
| LOG_MODULE_REGISTER(hawkbit, CONFIG_HAWKBIT_LOG_LEVEL); |
| |
| #define RECV_BUFFER_SIZE 640 |
| #define URL_BUFFER_SIZE 300 |
| #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) |
| |
| #define HAWKBIT_JSON_URL "/" CONFIG_HAWKBIT_TENANT "/controller/v1" |
| |
| #define HTTP_HEADER_CONTENT_TYPE_JSON "application/json;charset=UTF-8" |
| |
| #define SLOT1_LABEL slot1_partition |
| #define SLOT1_SIZE FIXED_PARTITION_SIZE(SLOT1_LABEL) |
| |
| static uint32_t poll_sleep = (CONFIG_HAWKBIT_POLL_INTERVAL * SEC_PER_MIN); |
| |
| static bool hawkbit_initialized; |
| |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| |
| #ifdef CONFIG_HAWKBIT_DDI_GATEWAY_SECURITY |
| #define AUTH_HEADER_START "Authorization: GatewayToken " |
| #else |
| #define AUTH_HEADER_START "Authorization: TargetToken " |
| #endif /* CONFIG_HAWKBIT_DDI_GATEWAY_SECURITY */ |
| |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| #define AUTH_HEADER_FULL AUTH_HEADER_START "%s" HTTP_CRLF |
| #else |
| #define AUTH_HEADER_FULL AUTH_HEADER_START CONFIG_HAWKBIT_DDI_SECURITY_TOKEN HTTP_CRLF |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| |
| #ifdef CONFIG_DNS_RESOLVER_MAX_QUERY_LEN |
| #define SERVER_ADDR_LEN CONFIG_DNS_RESOLVER_MAX_QUERY_LEN |
| #elif defined(CONFIG_NET_IPV6) |
| #define SERVER_ADDR_LEN INET6_ADDRSTRLEN |
| #else |
| #define SERVER_ADDR_LEN INET_ADDRSTRLEN |
| #endif |
| |
| static struct hawkbit_config { |
| int32_t action_id; |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| char server_addr[SERVER_ADDR_LEN + 1]; |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| char server_domain[CONFIG_HAWKBIT_DOMAIN_NAME_MAX_LEN + 1]; |
| #endif |
| char server_port[sizeof(STRINGIFY(__UINT16_MAX__))]; |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| char ddi_security_token[DDI_SECURITY_TOKEN_SIZE + 1]; |
| #endif |
| #ifdef CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG |
| sec_tag_t tls_tag; |
| #endif |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| } hb_cfg; |
| |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| #define HAWKBIT_SERVER_DOMAIN hb_cfg.server_domain |
| #else |
| #define HAWKBIT_SERVER_DOMAIN hb_cfg.server_addr |
| #endif /* CONFIG_HAWKBIT_USE_DOMAIN_NAME */ |
| #define HAWKBIT_SERVER_ADDR hb_cfg.server_addr |
| #define HAWKBIT_PORT hb_cfg.server_port |
| #define HAWKBIT_PORT_INT atoi(hb_cfg.server_port) |
| #else |
| #define HAWKBIT_SERVER_ADDR CONFIG_HAWKBIT_SERVER |
| #define HAWKBIT_SERVER_DOMAIN CONFIG_HAWKBIT_SERVER |
| #define HAWKBIT_PORT STRINGIFY(CONFIG_HAWKBIT_PORT) |
| #define HAWKBIT_PORT_INT CONFIG_HAWKBIT_PORT |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| |
| #ifdef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| #define HAWKBIT_DDI_SECURITY_TOKEN NULL |
| #elif defined(CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME) |
| #define HAWKBIT_DDI_SECURITY_TOKEN hb_cfg.ddi_security_token |
| #else |
| #define HAWKBIT_DDI_SECURITY_TOKEN CONFIG_HAWKBIT_DDI_SECURITY_TOKEN |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| |
| #ifdef CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG |
| #define HAWKBIT_CERT_TAG hb_cfg.tls_tag |
| #elif defined(HAWKBIT_USE_STATIC_CERT_TAG) |
| #define HAWKBIT_CERT_TAG CONFIG_HAWKBIT_STATIC_CERT_TAG |
| #else |
| #define HAWKBIT_CERT_TAG 0 |
| #endif /* CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG */ |
| |
| struct hawkbit_download { |
| size_t downloaded_size; |
| size_t http_content_size; |
| uint8_t file_hash[SHA256_HASH_SIZE]; |
| int32_t file_size; |
| }; |
| |
| union hawkbit_results { |
| struct hawkbit_dep_res dep; |
| struct hawkbit_ctl_res base; |
| }; |
| |
| struct hawkbit_context { |
| int sock; |
| uint8_t *response_data; |
| size_t response_data_size; |
| int32_t json_action_id; |
| struct hawkbit_download dl; |
| struct flash_img_context flash_ctx; |
| enum hawkbit_response code_status; |
| bool final_data_received; |
| enum hawkbit_http_request type; |
| union hawkbit_results results; |
| }; |
| |
| struct s_object { |
| struct smf_ctx ctx; |
| struct hawkbit_context hb_context; |
| char device_id[DEVICE_ID_HEX_MAX_SIZE]; |
| }; |
| |
| static const struct smf_state hawkbit_states[]; |
| |
| enum hawkbit_state { |
| S_HAWKBIT_START, |
| S_HAWKBIT_HTTP, |
| S_HAWKBIT_PROBE, |
| S_HAWKBIT_CONFIG_DEVICE, |
| S_HAWKBIT_CANCEL, |
| S_HAWKBIT_PROBE_DEPLOYMENT_BASE, |
| S_HAWKBIT_REPORT, |
| S_HAWKBIT_DOWNLOAD, |
| S_HAWKBIT_TERMINATE, |
| }; |
| |
| int hawkbit_default_config_data_cb(const char *device_id, uint8_t *buffer, |
| const size_t buffer_size); |
| |
| static hawkbit_config_device_data_cb_handler_t hawkbit_config_device_data_cb_handler = |
| hawkbit_default_config_data_cb; |
| |
| K_SEM_DEFINE(probe_sem, 1, 1); |
| |
| #ifdef CONFIG_HAWKBIT_EVENT_CALLBACKS |
| static sys_slist_t event_callbacks = SYS_SLIST_STATIC_INIT(&event_callbacks); |
| #endif |
| |
| 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), |
| }; |
| |
| 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), |
| }; |
| |
| static const struct json_obj_descr json_cancel_descr[] = { |
| JSON_OBJ_DESCR_OBJECT(struct hawkbit_cancel, 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_OBJECT(struct hawkbit_dep_fbk, status, json_status_descr), |
| }; |
| |
| static int hawkbit_settings_set(const char *name, size_t len, settings_read_cb read_cb, |
| void *cb_arg) |
| { |
| const char *next; |
| int rc; |
| |
| if (settings_name_steq(name, "action_id", &next) && !next) { |
| if (len != sizeof(hb_cfg.action_id)) { |
| return -EINVAL; |
| } |
| |
| rc = read_cb(cb_arg, &hb_cfg.action_id, sizeof(hb_cfg.action_id)); |
| LOG_DBG("<%s> = %d", "hawkbit/action_id", hb_cfg.action_id); |
| if (rc >= 0) { |
| return 0; |
| } |
| |
| return rc; |
| } |
| |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| if (settings_name_steq(name, "server_addr", &next) && !next) { |
| rc = read_cb(cb_arg, &hb_cfg.server_addr, MIN(len, sizeof(hb_cfg.server_addr))); |
| if (strnlen(hb_cfg.server_addr, sizeof(hb_cfg.server_addr)) == |
| sizeof(hb_cfg.server_addr)) { |
| memset(hb_cfg.server_addr, 0, sizeof(hb_cfg.server_addr)); |
| return -EINVAL; |
| } |
| LOG_DBG("<%s> = %s", "hawkbit/server_addr", hb_cfg.server_addr); |
| if (rc >= 0) { |
| return 0; |
| } |
| |
| return rc; |
| } |
| |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| if (settings_name_steq(name, "server_domain", &next) && !next) { |
| if (len != sizeof(hb_cfg.server_domain)) { |
| return -EINVAL; |
| } |
| |
| rc = read_cb(cb_arg, &hb_cfg.server_domain, sizeof(hb_cfg.server_domain)); |
| LOG_DBG("<%s> = %s", "hawkbit/server_domain", hb_cfg.server_domain); |
| if (rc >= 0) { |
| return 0; |
| } |
| |
| return rc; |
| } |
| #endif /* CONFIG_HAWKBIT_USE_DOMAIN_NAME */ |
| |
| if (settings_name_steq(name, "server_port", &next) && !next) { |
| if (len != sizeof(uint16_t)) { |
| return -EINVAL; |
| } |
| |
| uint16_t hawkbit_port = atoi(hb_cfg.server_port); |
| |
| rc = read_cb(cb_arg, &hawkbit_port, sizeof(hawkbit_port)); |
| if (hawkbit_port != atoi(hb_cfg.server_port)) { |
| snprintf(hb_cfg.server_port, sizeof(hb_cfg.server_port), "%u", |
| hawkbit_port); |
| } |
| LOG_DBG("<%s> = %s", "hawkbit/server_port", hb_cfg.server_port); |
| if (rc >= 0) { |
| return 0; |
| } |
| |
| return rc; |
| } |
| |
| if (settings_name_steq(name, "ddi_token", &next) && !next) { |
| #ifdef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| rc = read_cb(cb_arg, NULL, 0); |
| #else |
| if (len != sizeof(hb_cfg.ddi_security_token)) { |
| return -EINVAL; |
| } |
| |
| rc = read_cb(cb_arg, &hb_cfg.ddi_security_token, sizeof(hb_cfg.ddi_security_token)); |
| LOG_DBG("<%s> = %s", "hawkbit/ddi_token", hb_cfg.ddi_security_token); |
| if (rc >= 0) { |
| return 0; |
| } |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| return rc; |
| } |
| #else /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| if (settings_name_steq(name, "server_addr", NULL) || |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| settings_name_steq(name, "server_domain", NULL) || |
| #endif /* CONFIG_HAWKBIT_USE_DOMAIN_NAME */ |
| settings_name_steq(name, "server_port", NULL) || |
| settings_name_steq(name, "ddi_token", NULL)) { |
| rc = read_cb(cb_arg, NULL, 0); |
| 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; |
| } |
| |
| static int hawkbit_settings_export(int (*cb)(const char *name, const void *value, size_t val_len)) |
| { |
| LOG_DBG("export hawkbit settings"); |
| (void)cb("hawkbit/action_id", &hb_cfg.action_id, sizeof(hb_cfg.action_id)); |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| (void)cb("hawkbit/server_addr", &hb_cfg.server_addr, strlen(hb_cfg.server_addr) + 1); |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| (void)cb("hawkbit/server_domain", &hb_cfg.server_domain, sizeof(hb_cfg.server_domain)); |
| #endif /* CONFIG_HAWKBIT_USE_DOMAIN_NAME */ |
| uint16_t hawkbit_port = atoi(hb_cfg.server_port); |
| (void)cb("hawkbit/server_port", &hawkbit_port, sizeof(hawkbit_port)); |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| (void)cb("hawkbit/ddi_token", &hb_cfg.ddi_security_token, |
| sizeof(hb_cfg.ddi_security_token)); |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| return 0; |
| } |
| |
| SETTINGS_STATIC_HANDLER_DEFINE(hawkbit, "hawkbit", NULL, hawkbit_settings_set, NULL, |
| hawkbit_settings_export); |
| |
| static void hawkbit_event_raise(enum hawkbit_event_type event) |
| { |
| #ifdef CONFIG_HAWKBIT_EVENT_CALLBACKS |
| struct hawkbit_event_callback *cb, *tmp; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&event_callbacks, cb, tmp, node) { |
| if (cb->event == event && cb->handler) { |
| cb->handler(cb, event); |
| } |
| } |
| #endif |
| } |
| |
| #ifdef CONFIG_HAWKBIT_EVENT_CALLBACKS |
| int hawkbit_event_add_callback(struct hawkbit_event_callback *cb) |
| { |
| int ret = 0; |
| |
| if (cb == NULL || cb->handler == NULL) { |
| return -EINVAL; |
| } |
| |
| ret = k_sem_take(&probe_sem, K_FOREVER); |
| if (ret == 0) { |
| sys_slist_prepend(&event_callbacks, &cb->node); |
| k_sem_give(&probe_sem); |
| } |
| return ret; |
| } |
| |
| int hawkbit_event_remove_callback(struct hawkbit_event_callback *cb) |
| { |
| int ret = 0; |
| |
| if (cb == NULL || cb->handler == NULL) { |
| return -EINVAL; |
| } |
| |
| ret = k_sem_take(&probe_sem, K_FOREVER); |
| if (ret == 0) { |
| if (!sys_slist_find_and_remove(&event_callbacks, &cb->node)) { |
| ret = -EINVAL; |
| } |
| k_sem_give(&probe_sem); |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_HAWKBIT_EVENT_CALLBACKS */ |
| |
| static bool start_http_client(int *hb_sock) |
| { |
| int ret = -1; |
| struct zsock_addrinfo *addr; |
| struct zsock_addrinfo hints = {0}; |
| int resolve_attempts = 10; |
| int protocol = IS_ENABLED(CONFIG_HAWKBIT_USE_TLS) ? IPPROTO_TLS_1_2 : IPPROTO_TCP; |
| |
| if (IS_ENABLED(CONFIG_NET_IPV6)) { |
| hints.ai_family = AF_INET6; |
| hints.ai_socktype = SOCK_STREAM; |
| } else if (IS_ENABLED(CONFIG_NET_IPV4)) { |
| hints.ai_family = AF_INET; |
| hints.ai_socktype = SOCK_STREAM; |
| } |
| |
| while (resolve_attempts--) { |
| ret = zsock_getaddrinfo(HAWKBIT_SERVER_ADDR, HAWKBIT_PORT, &hints, &addr); |
| if (ret == 0) { |
| break; |
| } |
| |
| k_sleep(K_MSEC(1)); |
| } |
| |
| if (ret != 0) { |
| LOG_ERR("Failed to resolve dns: %d", ret); |
| return false; |
| } |
| |
| *hb_sock = zsock_socket(addr->ai_family, SOCK_STREAM, protocol); |
| if (*hb_sock < 0) { |
| LOG_ERR("Failed to create TCP socket"); |
| goto err; |
| } |
| |
| #ifdef CONFIG_HAWKBIT_USE_TLS |
| sec_tag_t sec_tag_opt[] = { |
| HAWKBIT_CERT_TAG, |
| }; |
| |
| if (zsock_setsockopt(*hb_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 (zsock_setsockopt(*hb_sock, SOL_TLS, TLS_HOSTNAME, HAWKBIT_SERVER_DOMAIN, |
| sizeof(HAWKBIT_SERVER_DOMAIN)) < 0) { |
| goto err_sock; |
| } |
| #endif /* CONFIG_HAWKBIT_USE_TLS */ |
| |
| if (zsock_connect(*hb_sock, addr->ai_addr, addr->ai_addrlen) < 0) { |
| LOG_ERR("Failed to connect to server"); |
| goto err_sock; |
| } |
| |
| zsock_freeaddrinfo(addr); |
| return true; |
| |
| err_sock: |
| zsock_close(*hb_sock); |
| err: |
| zsock_freeaddrinfo(addr); |
| return false; |
| } |
| |
| static void cleanup_connection(int *hb_sock) |
| { |
| if (zsock_close(*hb_sock) < 0) { |
| LOG_ERR("Failed to 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; |
| hb_cfg.action_id = new_value; |
| |
| ret = settings_save_one("hawkbit/action_id", &hb_cfg.action_id, sizeof(hb_cfg.action_id)); |
| if (ret < 0) { |
| LOG_ERR("Failed to write device id: %d", ret); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| int hawkbit_reset_action_id(void) |
| { |
| int ret; |
| |
| if (k_sem_take(&probe_sem, K_NO_WAIT) == 0) { |
| ret = hawkbit_device_acid_update(0); |
| k_sem_give(&probe_sem); |
| return ret; |
| } |
| return -EAGAIN; |
| } |
| |
| int32_t hawkbit_get_action_id(void) |
| { |
| return hb_cfg.action_id; |
| } |
| |
| uint32_t hawkbit_get_poll_interval(void) |
| { |
| return poll_sleep; |
| } |
| |
| /* |
| * Update sleep interval, based on results from hawkBit base polling |
| * resource |
| */ |
| static void hawkbit_update_sleep(struct hawkbit_ctl_res *hawkbit_res) |
| { |
| int 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 != sleep_time) { |
| LOG_DBG("New poll sleep %d seconds", sleep_time); |
| poll_sleep = (uint32_t)sleep_time; |
| } |
| } |
| } |
| |
| static char *hawkbit_get_url(const char *href) |
| { |
| char *helper; |
| |
| helper = strstr(href, "//"); |
| if (helper != NULL) { |
| helper = strstr(helper + 2u, "/"); |
| } |
| |
| if (!helper) { |
| LOG_ERR("Unexpected href format: %s", helper); |
| return NULL; |
| } |
| return helper; |
| } |
| |
| /* |
| * Find URL component for the device cancel action id |
| */ |
| static int hawkbit_find_cancel_action_id(struct hawkbit_ctl_res *res, int32_t *cancel_action_id) |
| { |
| char *helper; |
| |
| helper = strstr(res->_links.cancelAction.href, "cancelAction/"); |
| if (!helper) { |
| /* A badly formatted cancel base is a server error */ |
| LOG_ERR("Missing %s/ in href %s", "cancelAction", res->_links.cancelAction.href); |
| return -EINVAL; |
| } |
| |
| helper += sizeof("cancelAction/") - 1; |
| |
| *cancel_action_id = strtol(helper, NULL, 10); |
| if (*cancel_action_id <= 0) { |
| LOG_ERR("Invalid action_id: %d", *cancel_action_id); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int hawkbit_deployment_get_action_id(struct hawkbit_dep_res *res, int32_t *action_id) |
| { |
| int32_t id; |
| |
| id = strtol(res->id, NULL, 10); |
| if (id <= 0) { |
| LOG_ERR("Invalid action_id: %d", id); |
| return -EINVAL; |
| } |
| |
| *action_id = id; |
| |
| return 0; |
| } |
| |
| /* |
| * Find URL component for this device's deployment operations |
| * resource. |
| */ |
| static int hawkbit_parse_deployment(struct hawkbit_context *hb_context, struct hawkbit_dep_res *res, |
| char **download_http) |
| { |
| const char *href; |
| struct hawkbit_dep_res_chunk *chunk; |
| size_t num_chunks, num_artifacts; |
| struct hawkbit_dep_res_arts *artifact; |
| |
| num_chunks = res->deployment.num_chunks; |
| if (num_chunks != 1) { |
| LOG_ERR("Expecting 1 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 1 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; |
| } |
| |
| hb_context->dl.file_size = artifact->size; |
| |
| if (hb_context->dl.file_size > SLOT1_SIZE) { |
| LOG_ERR("Artifact file size too big (got %d, max is %d)", hb_context->dl.file_size, |
| SLOT1_SIZE); |
| return -ENOSPC; |
| } |
| |
| /* |
| * Find the download-http href. |
| */ |
| href = artifact->_links.download_http.href; |
| if (!href) { |
| LOG_ERR("Missing expected %s href", "download-http"); |
| return -EINVAL; |
| } |
| |
| /* Success. */ |
| if (download_http != NULL) { |
| *download_http = hawkbit_get_url(href); |
| if (*download_http == NULL) { |
| LOG_ERR("Failed to parse %s url", "deploymentBase"); |
| return -EINVAL; |
| } |
| } |
| return 0; |
| } |
| |
| 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("%s=%s", "id", d->id); |
| LOG_DBG("%s=%s", "download", d->deployment.download); |
| LOG_DBG("%s=%s", "update", d->deployment.update); |
| LOG_DBG("chunks[0].%s=%s", "part", c->part); |
| LOG_DBG("chunks[0].%s=%s", "name", c->name); |
| LOG_DBG("chunks[0].%s=%s", "version", c->version); |
| LOG_DBG("chunks[0].artifacts[0].%s=%s", "filename", a->filename); |
| LOG_DBG("chunks[0].artifacts[0].%s=%s", "hashes.sha1", a->hashes.sha1); |
| LOG_DBG("chunks[0].artifacts[0].%s=%s", "hashes.md5", a->hashes.md5); |
| LOG_DBG("chunks[0].artifacts[0].%s=%s", "hashes.sha256", a->hashes.sha256); |
| LOG_DBG("chunks[0].size=%d", a->size); |
| LOG_DBG("%s=%s", "download-http", l->download_http.href); |
| LOG_DBG("%s=%s", "md5sum-http", l->md5sum_http.href); |
| } |
| |
| int hawkbit_set_custom_data_cb(hawkbit_config_device_data_cb_handler_t cb) |
| { |
| if (IS_ENABLED(CONFIG_HAWKBIT_CUSTOM_ATTRIBUTES)) { |
| if (cb == NULL) { |
| LOG_ERR("Invalid callback"); |
| return -EINVAL; |
| } |
| |
| hawkbit_config_device_data_cb_handler = cb; |
| |
| return 0; |
| } |
| return -ENOTSUP; |
| } |
| |
| int hawkbit_default_config_data_cb(const char *device_id, uint8_t *buffer, |
| const size_t buffer_size) |
| { |
| struct hawkbit_cfg cfg = { |
| .mode = "merge", |
| .data.VIN = device_id, |
| }; |
| |
| return json_obj_encode_buf(json_cfg_descr, ARRAY_SIZE(json_cfg_descr), &cfg, buffer, |
| buffer_size); |
| } |
| |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| int hawkbit_set_config(struct hawkbit_runtime_config *config) |
| { |
| if (k_sem_take(&probe_sem, HAWKBIT_SET_SERVER_TIMEOUT) == 0) { |
| if (config->server_addr != NULL) { |
| if (strnlen(config->server_addr, sizeof(hb_cfg.server_addr)) == |
| sizeof(hb_cfg.server_addr)) { |
| LOG_ERR("%s too long: %s", "hawkbit/server_addr", |
| config->server_addr); |
| return -EINVAL; |
| } |
| strncpy(hb_cfg.server_addr, config->server_addr, |
| sizeof(hb_cfg.server_addr)); |
| LOG_DBG("configured %s: %s", "hawkbit/server_addr", hb_cfg.server_addr); |
| } |
| #ifdef CONFIG_HAWKBIT_USE_DOMAIN_NAME |
| if (config->server_domain != NULL) { |
| if (strnlen(config->server_domain, CONFIG_HAWKBIT_DOMAIN_NAME_MAX_LEN + 1) |
| > CONFIG_HAWKBIT_DOMAIN_NAME_MAX_LEN) { |
| LOG_ERR("%s too long: %s", "hawkbit/server_domain", |
| config->server_domain); |
| return -EINVAL; |
| } |
| strncpy(hb_cfg.server_domain, config->server_domain, |
| sizeof(hb_cfg.server_domain)); |
| LOG_DBG("configured %s: %s", "hawkbit/server_domain", |
| hb_cfg.server_domain); |
| } |
| #endif /* CONFIG_HAWKBIT_USE_DOMAIN_NAME */ |
| if (config->server_port != 0) { |
| snprintf(hb_cfg.server_port, sizeof(hb_cfg.server_port), "%u", |
| config->server_port); |
| LOG_DBG("configured %s: %s", "hawkbit/server_port", hb_cfg.server_port); |
| } |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| if (config->auth_token != NULL) { |
| strncpy(hb_cfg.ddi_security_token, config->auth_token, |
| sizeof(hb_cfg.ddi_security_token)); |
| LOG_DBG("configured %s: %s", "hawkbit/ddi_token", |
| hb_cfg.ddi_security_token); |
| } |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| #ifdef CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG |
| if (config->tls_tag != 0) { |
| hb_cfg.tls_tag = config->tls_tag; |
| LOG_DBG("configured %s: %d", "hawkbit/tls_tag", hb_cfg.tls_tag); |
| } |
| #endif /* CONFIG_HAWKBIT_USE_DYNAMIC_CERT_TAG */ |
| settings_save_subtree("hawkbit"); |
| k_sem_give(&probe_sem); |
| } else { |
| LOG_WRN("failed setting config"); |
| return -EAGAIN; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| |
| struct hawkbit_runtime_config hawkbit_get_config(void) |
| { |
| struct hawkbit_runtime_config config = { |
| .server_addr = HAWKBIT_SERVER_ADDR, |
| .server_port = HAWKBIT_PORT_INT, |
| .auth_token = HAWKBIT_DDI_SECURITY_TOKEN, |
| .tls_tag = HAWKBIT_CERT_TAG, |
| }; |
| |
| return config; |
| } |
| |
| int hawkbit_init(void) |
| { |
| bool image_ok; |
| int ret = 0; |
| |
| if (hawkbit_initialized) { |
| return 0; |
| } |
| |
| ret = settings_subsys_init(); |
| if (ret < 0) { |
| LOG_ERR("Failed to initialize settings subsystem: %d", ret); |
| return ret; |
| } |
| |
| ret = settings_load_subtree("hawkbit"); |
| if (ret < 0) { |
| LOG_ERR("Failed to load settings: %d", ret); |
| return ret; |
| } |
| |
| LOG_DBG("Current action_id: %d", hb_cfg.action_id); |
| |
| image_ok = boot_is_img_confirmed(); |
| LOG_INF("Current image is%s confirmed", image_ok ? "" : " not"); |
| if (!image_ok) { |
| ret = boot_write_img_confirmed(); |
| if (ret < 0) { |
| LOG_ERR("Failed to confirm current image: %d", ret); |
| return ret; |
| } |
| |
| LOG_DBG("Marked current image as OK"); |
| ret = boot_erase_img_bank(flash_img_get_upload_slot()); |
| if (ret < 0) { |
| LOG_ERR("Failed to erase second slot: %d", ret); |
| return ret; |
| } |
| |
| hawkbit_event_raise(HAWKBIT_EVENT_CONFIRMED_CURRENT_IMAGE); |
| } |
| hawkbit_initialized = true; |
| |
| return ret; |
| } |
| |
| static void response_json_cb(struct http_response *rsp, enum http_final_call final_data, |
| struct hawkbit_context *hb_context) |
| { |
| size_t body_len; |
| int ret; |
| uint8_t *body_data = NULL, *rsp_tmp = NULL; |
| |
| 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) > 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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| } |
| } |
| } |
| |
| 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) { |
| return; |
| } |
| |
| 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; |
| 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 |
| |
| hb_context->dl.downloaded_size = flash_img_bytes_written(&hb_context->flash_ctx); |
| |
| 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; |
| } |
| } |
| |
| static int 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 0; |
| } |
| |
| 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; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static bool send_request(struct hawkbit_context *hb_context, enum hawkbit_http_request type, |
| char *url_buffer, uint8_t *status_buffer) |
| { |
| int ret = 0; |
| uint8_t recv_buf_tcp[RECV_BUFFER_SIZE] = {0}; |
| struct http_request http_req = {0}; |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| #ifdef CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME |
| char header[DDI_SECURITY_TOKEN_SIZE + sizeof(AUTH_HEADER_START) + sizeof(HTTP_CRLF) - 1]; |
| |
| snprintf(header, sizeof(header), AUTH_HEADER_FULL, hb_cfg.ddi_security_token); |
| const char *const headers[] = {header, NULL}; |
| #else |
| static const char *const headers[] = {AUTH_HEADER_FULL, NULL}; |
| #endif /* CONFIG_HAWKBIT_SET_SETTINGS_RUNTIME */ |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| |
| http_req.url = url_buffer; |
| http_req.host = HAWKBIT_SERVER_DOMAIN; |
| http_req.port = HAWKBIT_PORT; |
| http_req.protocol = "HTTP/1.1"; |
| http_req.response = response_cb; |
| http_req.recv_buf = recv_buf_tcp; |
| http_req.recv_buf_len = sizeof(recv_buf_tcp); |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| http_req.header_fields = (const char **)headers; |
| #endif |
| hb_context->final_data_received = false; |
| hb_context->type = type; |
| |
| switch (type) { |
| case HAWKBIT_CONFIG_DEVICE: |
| /* |
| * Feedback channel for the config data action |
| * PUT: /{tenant}/controller/v1/{controllerId}/configData |
| */ |
| http_req.method = HTTP_PUT; |
| http_req.content_type_value = HTTP_HEADER_CONTENT_TYPE_JSON; |
| http_req.payload = status_buffer; |
| http_req.payload_len = strlen(status_buffer); |
| |
| break; |
| |
| case HAWKBIT_CANCEL: |
| /* |
| * Feedback channel for cancel actions |
| * POST: /{tenant}/controller/v1/{controllerId}/cancelAction/{actionId}/feedback |
| */ |
| case HAWKBIT_REPORT: |
| /* |
| * Feedback channel for the DeploymentBase action |
| * POST: /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback |
| */ |
| http_req.method = HTTP_POST; |
| http_req.content_type_value = HTTP_HEADER_CONTENT_TYPE_JSON; |
| http_req.payload = status_buffer; |
| http_req.payload_len = strlen(status_buffer); |
| |
| break; |
| |
| case HAWKBIT_PROBE: |
| /* |
| * Root resource for an individual Target |
| * GET: /{tenant}/controller/v1/{controllerId} |
| */ |
| case HAWKBIT_PROBE_DEPLOYMENT_BASE: |
| /* |
| * 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) |
| * GET: /{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/ |
| * artifacts/{fileName} |
| */ |
| 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; |
| |
| default: |
| return false; |
| } |
| |
| ret = http_client_req(hb_context->sock, &http_req, HAWKBIT_RECV_TIMEOUT, hb_context); |
| if (ret < 0) { |
| LOG_ERR("Failed to send request: %d", ret); |
| hb_context->code_status = HAWKBIT_NETWORKING_ERROR; |
| return false; |
| } |
| |
| if (IN_RANGE(hb_context->code_status, HAWKBIT_NETWORKING_ERROR, HAWKBIT_ALLOC_ERROR)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void hawkbit_reboot(void) |
| { |
| hawkbit_event_raise(HAWKBIT_EVENT_BEFORE_REBOOT); |
| LOG_PANIC(); |
| sys_reboot(IS_ENABLED(CONFIG_HAWKBIT_REBOOT_COLD) ? SYS_REBOOT_COLD : SYS_REBOOT_WARM); |
| } |
| |
| static bool check_hawkbit_server(void) |
| { |
| if (strlen(HAWKBIT_SERVER_ADDR) == 0) { |
| if (sizeof(CONFIG_HAWKBIT_SERVER) > 1) { |
| hawkbit_set_server_addr(CONFIG_HAWKBIT_SERVER); |
| } else { |
| LOG_ERR("no valid %s found", "hawkbit/server_addr"); |
| return false; |
| } |
| } |
| |
| if (HAWKBIT_PORT_INT == 0) { |
| if (CONFIG_HAWKBIT_PORT > 0) { |
| hawkbit_set_server_port(CONFIG_HAWKBIT_PORT); |
| } else { |
| LOG_ERR("no valid %s found", "hawkbit/server_port"); |
| return false; |
| } |
| } |
| |
| #ifndef CONFIG_HAWKBIT_DDI_NO_SECURITY |
| if (strlen(HAWKBIT_DDI_SECURITY_TOKEN) == 0) { |
| if (sizeof(CONFIG_HAWKBIT_DDI_SECURITY_TOKEN) > 1) { |
| hawkbit_set_ddi_security_token(CONFIG_HAWKBIT_DDI_SECURITY_TOKEN); |
| } else { |
| LOG_ERR("no valid %s found", "hawkbit/ddi_token"); |
| return false; |
| } |
| } |
| #endif /* CONFIG_HAWKBIT_DDI_NO_SECURITY */ |
| |
| return true; |
| } |
| |
| static void s_start(void *o) |
| { |
| struct s_object *s = (struct s_object *)o; |
| |
| if (!hawkbit_initialized) { |
| smf_set_terminate(SMF_CTX(s), HAWKBIT_NOT_INITIALIZED); |
| return; |
| } |
| |
| if (!check_hawkbit_server()) { |
| smf_set_terminate(SMF_CTX(s), HAWKBIT_NETWORKING_ERROR); |
| return; |
| } |
| |
| if (k_sem_take(&probe_sem, K_NO_WAIT) != 0) { |
| LOG_INF("hawkBit is already running"); |
| smf_set_terminate(SMF_CTX(s), HAWKBIT_PROBE_IN_PROGRESS); |
| return; |
| } |
| |
| if (!boot_is_img_confirmed()) { |
| LOG_ERR("Current image is not confirmed"); |
| k_sem_give(&probe_sem); |
| smf_set_terminate(SMF_CTX(s), HAWKBIT_UNCONFIRMED_IMAGE); |
| return; |
| } |
| |
| if (!hawkbit_get_device_identity(s->device_id, DEVICE_ID_HEX_MAX_SIZE)) { |
| k_sem_give(&probe_sem); |
| smf_set_terminate(SMF_CTX(s), HAWKBIT_METADATA_ERROR); |
| return; |
| } |
| } |
| |
| static void s_end(void *o) |
| { |
| ARG_UNUSED(o); |
| k_sem_give(&probe_sem); |
| } |
| |
| static void s_http_start(void *o) |
| { |
| struct s_object *s = (struct s_object *)o; |
| |
| if (!start_http_client(&s->hb_context.sock)) { |
| s->hb_context.code_status = HAWKBIT_NETWORKING_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| } |
| |
| s->hb_context.response_data_size = RESPONSE_BUFFER_SIZE; |
| |
| s->hb_context.response_data = k_calloc(s->hb_context.response_data_size, sizeof(uint8_t)); |
| if (s->hb_context.response_data == NULL) { |
| cleanup_connection(&s->hb_context.sock); |
| s->hb_context.code_status = HAWKBIT_ALLOC_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| } |
| } |
| |
| static void s_http_end(void *o) |
| { |
| struct s_object *s = (struct s_object *)o; |
| |
| cleanup_connection(&s->hb_context.sock); |
| k_free(s->hb_context.response_data); |
| } |
| |
| /* |
| * Root resource for an individual Target |
| * GET: /{tenant}/controller/v1/{controllerId} |
| */ |
| static enum smf_state_result s_probe(void *o) |
| { |
| struct s_object *s = (struct s_object *)o; |
| char url_buffer[URL_BUFFER_SIZE] = {0}; |
| |
| LOG_INF("Polling target data from hawkBit"); |
| |
| snprintk(url_buffer, sizeof(url_buffer), "%s/%s", HAWKBIT_JSON_URL, s->device_id); |
| |
| if (!send_request(&s->hb_context, HAWKBIT_PROBE, url_buffer, NULL)) { |
| LOG_ERR("Send request failed (%s)", "HAWKBIT_PROBE"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (s->hb_context.results.base.config.polling.sleep) { |
| /* Update the sleep time. */ |
| LOG_DBG("config.polling.sleep=%s", s->hb_context.results.base.config.polling.sleep); |
| hawkbit_update_sleep(&s->hb_context.results.base); |
| } |
| |
| if (s->hb_context.results.base._links.cancelAction.href) { |
| LOG_DBG("_links.%s.href=%s", "cancelAction", |
| s->hb_context.results.base._links.cancelAction.href); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_CANCEL]); |
| } else if (s->hb_context.results.base._links.configData.href) { |
| LOG_DBG("_links.%s.href=%s", "configData", |
| s->hb_context.results.base._links.configData.href); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_CONFIG_DEVICE]); |
| } else if (s->hb_context.results.base._links.deploymentBase.href) { |
| LOG_DBG("_links.%s.href=%s", "deploymentBase", |
| s->hb_context.results.base._links.deploymentBase.href); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_PROBE_DEPLOYMENT_BASE]); |
| } else { |
| s->hb_context.code_status = HAWKBIT_NO_UPDATE; |
| hawkbit_event_raise(HAWKBIT_EVENT_NO_UPDATE); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| } |
| return SMF_EVENT_PROPAGATE; |
| } |
| |
| /* |
| * Feedback channel for cancel actions |
| * POST: /{tenant}/controller/v1/{controllerId}/cancelAction/{actionId}/feedback |
| */ |
| static enum smf_state_result s_cancel(void *o) |
| { |
| int ret = 0; |
| int32_t cancel_action_id = 0; |
| struct s_object *s = (struct s_object *)o; |
| char *cancel_base; |
| uint8_t status_buffer[CONFIG_HAWKBIT_STATUS_BUFFER_SIZE] = {0}; |
| char url_buffer[URL_BUFFER_SIZE] = {0}; |
| struct hawkbit_cancel cancel = {0}; |
| |
| cancel_base = hawkbit_get_url(s->hb_context.results.base._links.cancelAction.href); |
| if (cancel_base == NULL) { |
| LOG_ERR("Can't find %s url", "cancelAction"); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| snprintk(url_buffer, sizeof(url_buffer), "%s/%s", cancel_base, "feedback"); |
| |
| ret = hawkbit_find_cancel_action_id(&s->hb_context.results.base, &cancel_action_id); |
| if (ret < 0) { |
| LOG_ERR("Can't find %s id: %d", "cancelAction", ret); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| cancel.status.execution = hawkbit_status_execution(HAWKBIT_STATUS_EXEC_CLOSED); |
| cancel.status.result.finished = hawkbit_status_finished( |
| hb_cfg.action_id == cancel_action_id ? HAWKBIT_STATUS_FINISHED_FAILURE |
| : HAWKBIT_STATUS_FINISHED_SUCCESS); |
| |
| ret = json_obj_encode_buf(json_cancel_descr, ARRAY_SIZE(json_cancel_descr), &cancel, |
| status_buffer, sizeof(status_buffer)); |
| if (ret) { |
| LOG_ERR("Can't encode the JSON script (%s): %d", "HAWKBIT_CANCEL", ret); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (!send_request(&s->hb_context, HAWKBIT_CANCEL, url_buffer, status_buffer)) { |
| LOG_ERR("Send request failed (%s)", "HAWKBIT_CANCEL"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| LOG_INF("From hawkBit server requested update cancellation %s", |
| hb_cfg.action_id == cancel_action_id ? "rejected" : "accepted"); |
| |
| if (hb_cfg.action_id != cancel_action_id) { |
| hawkbit_event_raise(HAWKBIT_EVENT_CANCEL_UPDATE); |
| } |
| |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_PROBE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* |
| * Feedback channel for the config data action |
| * PUT: /{tenant}/controller/v1/{controllerId}/configData |
| */ |
| static enum smf_state_result s_config_device(void *o) |
| { |
| int ret = 0; |
| struct s_object *s = (struct s_object *)o; |
| uint8_t status_buffer[CONFIG_HAWKBIT_STATUS_BUFFER_SIZE] = {0}; |
| char *url_buffer; |
| |
| url_buffer = hawkbit_get_url(s->hb_context.results.base._links.configData.href); |
| if (url_buffer == NULL) { |
| LOG_ERR("Can't find %s url", "configData"); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| ret = hawkbit_config_device_data_cb_handler(s->device_id, status_buffer, |
| sizeof(status_buffer)); |
| if (ret) { |
| LOG_ERR("Can't encode the JSON script (%s): %d", "HAWKBIT_CONFIG_DEVICE", ret); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (!send_request(&s->hb_context, HAWKBIT_CONFIG_DEVICE, url_buffer, status_buffer)) { |
| LOG_ERR("Send request failed (%s)", "HAWKBIT_CONFIG_DEVICE"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_PROBE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* |
| * Resource for software module (Deployment Base) |
| * GET: /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId} |
| */ |
| static enum smf_state_result s_probe_deployment_base(void *o) |
| { |
| int ret = 0; |
| struct s_object *s = (struct s_object *)o; |
| char *url_buffer; |
| |
| url_buffer = hawkbit_get_url(s->hb_context.results.base._links.deploymentBase.href); |
| if (url_buffer == NULL) { |
| LOG_ERR("Can't find %s url", "deploymentBase"); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (!send_request(&s->hb_context, HAWKBIT_PROBE_DEPLOYMENT_BASE, url_buffer, NULL)) { |
| LOG_ERR("Send request failed (%s)", "HAWKBIT_PROBE_DEPLOYMENT_BASE"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| hawkbit_dump_deployment(&s->hb_context.results.dep); |
| |
| ret = hawkbit_deployment_get_action_id(&s->hb_context.results.dep, |
| &s->hb_context.json_action_id); |
| if (ret < 0) { |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (hb_cfg.action_id == s->hb_context.json_action_id) { |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_REPORT]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| LOG_INF("Ready to download update"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_DOWNLOAD]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* |
| * Feedback channel for the DeploymentBase action |
| * POST: /{tenant}/controller/v1/{controllerId}/deploymentBase/{actionId}/feedback |
| */ |
| static enum smf_state_result s_report(void *o) |
| { |
| int ret = 0; |
| struct s_object *s = (struct s_object *)o; |
| struct hawkbit_dep_fbk feedback = { |
| .status.execution = hawkbit_status_execution(HAWKBIT_STATUS_EXEC_CLOSED), |
| .status.result.finished = hawkbit_status_finished(HAWKBIT_STATUS_FINISHED_SUCCESS), |
| }; |
| uint8_t status_buffer[CONFIG_HAWKBIT_STATUS_BUFFER_SIZE] = {0}; |
| char url_buffer[URL_BUFFER_SIZE] = {0}; |
| |
| snprintk(url_buffer, sizeof(url_buffer), "%s/%s/%s/%d/%s", HAWKBIT_JSON_URL, |
| s->device_id, "deploymentBase", s->hb_context.json_action_id, "feedback"); |
| |
| LOG_INF("Reporting deployment feedback %s (%s) for action %d", |
| feedback.status.result.finished, feedback.status.execution, |
| s->hb_context.json_action_id); |
| |
| ret = json_obj_encode_buf(json_dep_fbk_descr, ARRAY_SIZE(json_dep_fbk_descr), &feedback, |
| status_buffer, sizeof(status_buffer)); |
| if (ret) { |
| LOG_ERR("Can't encode the JSON script (%s): %d", "HAWKBIT_REPORT", ret); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (!send_request(&s->hb_context, HAWKBIT_REPORT, url_buffer, status_buffer)) { |
| LOG_ERR("Send request failed (%s)", "HAWKBIT_REPORT"); |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* After reporting the successful update to the hawkBit server, we can reset the saved |
| * action ID to 0, as we don't need it anymore. |
| */ |
| (void)hawkbit_device_acid_update(0); |
| |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_PROBE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void s_download_start(void *o) |
| { |
| hawkbit_event_raise(HAWKBIT_EVENT_START_DOWNLOAD); |
| } |
| |
| static void s_download_end(void *o) |
| { |
| hawkbit_event_raise(HAWKBIT_EVENT_END_DOWNLOAD); |
| } |
| |
| /* |
| * Resource for software module (Deployment Base) |
| * GET: /{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/ |
| * artifacts/{fileName} |
| */ |
| static enum smf_state_result s_download(void *o) |
| { |
| int ret = 0; |
| struct s_object *s = (struct s_object *)o; |
| struct flash_img_check fic = {0}; |
| const struct flash_area *flash_area_ptr; |
| char *url_buffer; |
| |
| ret = hawkbit_parse_deployment(&s->hb_context, &s->hb_context.results.dep, &url_buffer); |
| if (ret < 0) { |
| LOG_ERR("Failed to parse %s: %d", "deploymentBase", ret); |
| s->hb_context.code_status = HAWKBIT_METADATA_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| flash_img_init(&s->hb_context.flash_ctx); |
| |
| /* The flash_area pointer has to be copied before the download starts |
| * because the flash_area will be set to NULL after the download has finished. |
| */ |
| 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]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* Check if download finished */ |
| if (!s->hb_context.final_data_received) { |
| LOG_ERR("Download incomplete"); |
| s->hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| #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; |
| if (flash_img_check(&s->hb_context.flash_ctx, &fic, flash_area_ptr->fa_id)) { |
| LOG_ERR("Failed to validate stored firmware"); |
| s->hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* Request mcuboot to upgrade */ |
| if (boot_set_next(flash_area_ptr, false, false)) { |
| LOG_ERR("Failed to mark the image in slot 1 as pending"); |
| s->hb_context.code_status = HAWKBIT_DOWNLOAD_ERROR; |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* If everything is successful */ |
| s->hb_context.code_status = HAWKBIT_UPDATE_INSTALLED; |
| hawkbit_device_acid_update(s->hb_context.json_action_id); |
| hawkbit_event_raise(HAWKBIT_EVENT_UPDATE_DOWNLOADED); |
| |
| smf_set_state(SMF_CTX(s), &hawkbit_states[S_HAWKBIT_TERMINATE]); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void s_terminate(void *o) |
| { |
| struct s_object *s = (struct s_object *)o; |
| |
| #ifdef CONFIG_HAWKBIT_EVENT_CALLBACKS |
| if (IN_RANGE(s->hb_context.code_status, HAWKBIT_NETWORKING_ERROR, |
| HAWKBIT_PROBE_IN_PROGRESS)) { |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR); |
| switch (s->hb_context.code_status) { |
| case HAWKBIT_NETWORKING_ERROR: |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR_NETWORKING); |
| break; |
| |
| case HAWKBIT_PERMISSION_ERROR: |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR_PERMISSION); |
| break; |
| |
| case HAWKBIT_METADATA_ERROR: |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR_METADATA); |
| break; |
| |
| case HAWKBIT_DOWNLOAD_ERROR: |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR_DOWNLOAD); |
| break; |
| |
| case HAWKBIT_ALLOC_ERROR: |
| hawkbit_event_raise(HAWKBIT_EVENT_ERROR_ALLOC); |
| break; |
| default: |
| break; |
| } |
| } |
| #endif |
| |
| smf_set_terminate(SMF_CTX(s), s->hb_context.code_status); |
| } |
| |
| /* clang-format off */ |
| static const struct smf_state hawkbit_states[] = { |
| [S_HAWKBIT_START] = SMF_CREATE_STATE( |
| s_start, |
| NULL, |
| s_end, |
| NULL, |
| NULL), |
| [S_HAWKBIT_HTTP] = SMF_CREATE_STATE( |
| s_http_start, |
| NULL, |
| s_http_end, |
| &hawkbit_states[S_HAWKBIT_START], |
| NULL), |
| [S_HAWKBIT_PROBE] = SMF_CREATE_STATE( |
| NULL, |
| s_probe, |
| NULL, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_CONFIG_DEVICE] = SMF_CREATE_STATE( |
| NULL, |
| s_config_device, |
| NULL, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_CANCEL] = SMF_CREATE_STATE( |
| NULL, |
| s_cancel, |
| NULL, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_PROBE_DEPLOYMENT_BASE] = SMF_CREATE_STATE( |
| NULL, |
| s_probe_deployment_base, |
| NULL, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_REPORT] = SMF_CREATE_STATE( |
| NULL, |
| s_report, |
| NULL, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_DOWNLOAD] = SMF_CREATE_STATE( |
| s_download_start, |
| s_download, |
| s_download_end, |
| &hawkbit_states[S_HAWKBIT_HTTP], |
| NULL), |
| [S_HAWKBIT_TERMINATE] = SMF_CREATE_STATE( |
| s_terminate, |
| NULL, |
| NULL, |
| NULL, |
| NULL), |
| }; |
| /* clang-format on */ |
| enum hawkbit_response hawkbit_probe(void) |
| { |
| int32_t ret = 0; |
| struct s_object s_obj = {0}; |
| |
| smf_set_initial(SMF_CTX(&s_obj), &hawkbit_states[S_HAWKBIT_PROBE]); |
| hawkbit_event_raise(HAWKBIT_EVENT_START_RUN); |
| |
| while (1) { |
| ret = smf_run_state(SMF_CTX(&s_obj)); |
| if (ret != 0) { |
| hawkbit_event_raise(HAWKBIT_EVENT_END_RUN); |
| return (enum hawkbit_response)ret; |
| } |
| } |
| } |