/* OTA 的平台无关部分(C2)。说明在 ota.h。 */ #include "ota.h" #include #include "hal_lock.h" static ota_start_fn s_start; static ota_reboot_fn s_reboot; static void *s_ctx; static ota_status_t s_st; void ota_init(ota_start_fn start, ota_reboot_fn reboot, void *ctx) { hal_crit_enter(); memset(&s_st, 0, sizeof(s_st)); s_start = start; s_reboot = reboot; s_ctx = ctx; hal_crit_exit(); } bool ota_url_ok(const char *url) { static const char k_scheme[] = "http://"; if (url == NULL || strncmp(url, k_scheme, sizeof(k_scheme) - 1) != 0) { return false; } const size_t n = strlen(url); if (n <= sizeof(k_scheme) - 1 || n > OTA_URL_MAX) { return false; } for (size_t i = 0; i < n; i++) { const unsigned char ch = (unsigned char)url[i]; if (ch <= 0x20 || ch >= 0x7F || ch == '"' || ch == '\\') { return false; } } return true; } static int hexval(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } return -1; /* 大写也算错:同一个哈希只许一种写法(ota.h) */ } bool ota_parse_sha256(const char *hex, uint8_t out[32]) { if (hex == NULL || strlen(hex) != 64) { return false; } uint8_t tmp[32]; for (int i = 0; i < 32; i++) { const int hi = hexval(hex[2 * i]); const int lo = hexval(hex[2 * i + 1]); if (hi < 0 || lo < 0) { return false; } tmp[i] = (uint8_t)((hi << 4) | lo); } memcpy(out, tmp, sizeof(tmp)); /* 半截错的不留在 out 里 */ return true; } void ota_sha256_hex(const uint8_t sha[32], char out[65]) { static const char k[] = "0123456789abcdef"; for (int i = 0; i < 32; i++) { out[2 * i] = k[sha[i] >> 4]; out[2 * i + 1] = k[sha[i] & 0x0F]; } out[64] = '\0'; } ota_action_t ota_plan(const ota_status_t *st, const ota_req_t *req) { if (st->phase == OTA_DL) { return OTA_ACT_BUSY; } if (st->phase == OTA_READY && memcmp(st->sha256, req->sha256, sizeof(st->sha256)) == 0) { return req->reboot ? OTA_ACT_REBOOT : OTA_ACT_NOTHING; } /* READY 了另一个镜像:重新下载会覆盖同一个备用槽,胶水里会先把启动分区切回当前的 (esp_ota_begin 本身就会让那个槽失效)—— 这里照常开工。 */ if (st->pending) { return OTA_ACT_PENDING; } return OTA_ACT_START; } int ota_request(const ota_req_t *req) { ota_status_t st; ota_status_get(&st); const ota_action_t a = ota_plan(&st, req); switch (a) { case OTA_ACT_START: if (s_start == NULL) { return -1; } /* 先把状态置成 DL 再交任务:命令处理器下一条 status 立刻看得到「在下载」, 也挡住了紧跟着的第二条 ota(它会拿到 BUSY)。 */ st.phase = OTA_DL; st.err = OTA_E_NONE; st.got = 0; st.size = req->size; memcpy(st.sha256, req->sha256, sizeof(st.sha256)); ota_status_set(&st); if (s_start(s_ctx, req) != HAL_OK) { st.phase = OTA_IDLE; ota_status_set(&st); return -1; } return 0; case OTA_ACT_REBOOT: if (s_reboot == NULL) { return -1; } s_reboot(s_ctx); return 0; case OTA_ACT_NOTHING: return 0; default: return (int)a; } } void ota_status_set(const ota_status_t *st) { hal_crit_enter(); s_st = *st; hal_crit_exit(); } void ota_status_get(ota_status_t *st) { hal_crit_enter(); *st = s_st; hal_crit_exit(); } const char *ota_phase_str(ota_phase_t p) { switch (p) { case OTA_IDLE: return "idle"; case OTA_DL: return "dl"; case OTA_READY: return "ready"; default: return "?"; } } const char *ota_err_str(ota_err_t e) { static const char *const k[OTA_E_COUNT] = { [OTA_E_NONE] = "", [OTA_E_HTTP] = "http", [OTA_E_SIZE] = "size", [OTA_E_HASH] = "hash", [OTA_E_FLASH] = "flash", [OTA_E_IMAGE] = "image", [OTA_E_BOOT] = "boot", [OTA_E_NOSLOT] = "noslot", }; return ((unsigned)e < OTA_E_COUNT) ? k[e] : "?"; } ota_health_t ota_health(const ota_health_in_t *in) { if (!in->pending) { return OTA_H_NONE; } bool net_ok; if (in->mqtt_set) { net_ok = in->mqtt_up; } else if (in->wifi_set) { net_ok = in->wifi_up; } else { net_ok = true; } if (in->boot_done && net_ok) { return OTA_H_VALID; } return (in->now_ms >= OTA_HEALTH_MS) ? OTA_H_ROLLBACK : OTA_H_WAIT; }