diff --git a/tests/unit/test_slot_manager.c b/tests/unit/test_slot_manager.c index ec12af6..53a864b 100644 --- a/tests/unit/test_slot_manager.c +++ b/tests/unit/test_slot_manager.c @@ -5,19 +5,10 @@ /** * @file test_slot_manager.c * @brief Unit tests for the production firmware slot manager - * - * These tests exercise the real core/slot_manager.c. The three image - * verification steps it calls -- eos_image_parse_header(), - * eos_image_verify_integrity() and eos_image_verify_signature() -- are - * replaced with per-slot scriptable mocks so each stage can be failed - * independently without having to build and sign real images. Flash and - * slot geometry come from the standard simulated eos_board_ops_t, so - * eos_hal_slot_addr()/eos_hal_slot_size() behave as they do on a board. */ -#include "eos_slot_manager.h" -#include "eos_image.h" #include "eos_hal.h" +#include "eos_slot_manager.h" #include #include #include @@ -26,103 +17,70 @@ #define SLOT_B_ADDR 0x30000u #define SLOT_SIZE 0x10000u -/* ---- Simulated flash / board ops ---- - * - * Large enough to hold both slots (SLOT_B_ADDR + SLOT_SIZE) plus the low - * bootctl/backup/log region every other test file reserves below 0x4000. */ -#define SIM_FLASH_SIZE (SLOT_B_ADDR + SLOT_SIZE) -static uint8_t sim_flash[SIM_FLASH_SIZE]; - -/* ---- Observed/scripted flash erase behaviour (read by sim_flash_erase - * below, scripted/checked by the erase tests further down) ---- */ -static int erase_result; +static int parse_result[2]; +static int integrity_result[2]; +static int signature_result[2]; +static uint32_t slot_version[2]; +static int erase_result; static uint32_t erased_addr; -static size_t erased_size; +static size_t erased_size; +static int tests_passed; + +#define ASSERT(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "[FAIL] %s:%d: %s\n", \ + __FILE__, __LINE__, #condition); \ + exit(1); \ + } \ + } while (0) + +#define RUN(test) \ + do { \ + reset_fixture(); \ + test(); \ + tests_passed++; \ + printf("[PASS] %s\n", #test); \ + } while (0) -static int sim_flash_read(uint32_t addr, void *buf, size_t len) +static int slot_index(uint32_t addr) { - if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; - memcpy(buf, &sim_flash[addr], len); - return EOS_OK; + if (addr == SLOT_A_ADDR) return EOS_SLOT_A; + if (addr == SLOT_B_ADDR) return EOS_SLOT_B; + return -1; } -static int sim_flash_write(uint32_t addr, const void *buf, size_t len) +static void reset_fixture(void) { - if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; - memcpy(&sim_flash[addr], buf, len); - return EOS_OK; + for (int i = 0; i < 2; i++) { + parse_result[i] = EOS_ERR_NO_IMAGE; + integrity_result[i] = EOS_OK; + signature_result[i] = EOS_OK; + slot_version[i] = 0; + } + erase_result = EOS_OK; + erased_addr = 0; + erased_size = 0; } -static int sim_flash_erase(uint32_t addr, size_t len) +uint32_t eos_hal_slot_addr(eos_slot_t slot) { - erased_addr = addr; - erased_size = len; - if (erase_result != EOS_OK) return erase_result; - if (addr + len > SIM_FLASH_SIZE) return EOS_ERR_FLASH; - memset(&sim_flash[addr], 0xFF, len); - return EOS_OK; + if (slot == EOS_SLOT_A) return SLOT_A_ADDR; + if (slot == EOS_SLOT_B) return SLOT_B_ADDR; + return 0; } -static uint32_t sim_tick = 0; -static uint32_t sim_get_tick(void) { return sim_tick++; } -static void sim_noop(void) {} -static void sim_noop_u32(uint32_t x) { (void)x; } -static void sim_jump(uint32_t addr) { (void)addr; } -static eos_reset_reason_t sim_reset_reason(void) { return EOS_RESET_POWER_ON; } -static bool sim_recovery_pin(void) { return false; } -static void sim_system_reset(void) {} - -static const eos_board_ops_t sim_ops = { - .flash_base = 0, - .flash_size = SIM_FLASH_SIZE, - .slot_a_addr = SLOT_A_ADDR, - .slot_a_size = SLOT_SIZE, - .slot_b_addr = SLOT_B_ADDR, - .slot_b_size = SLOT_SIZE, - .recovery_addr = 0, - .recovery_size = 0, - .bootctl_addr = 0, - .bootctl_backup_addr = 0x1000, - .log_addr = 0x2000, - .app_vector_offset = 0, - .flash_read = sim_flash_read, - .flash_write = sim_flash_write, - .flash_erase = sim_flash_erase, - .watchdog_init = sim_noop_u32, - .watchdog_feed = sim_noop, - .get_reset_reason = sim_reset_reason, - .system_reset = sim_system_reset, - .recovery_pin_asserted = sim_recovery_pin, - .jump = sim_jump, - .uart_init = NULL, - .uart_send = NULL, - .uart_recv = NULL, - .get_tick_ms = sim_get_tick, - .disable_interrupts = sim_noop, - .enable_interrupts = sim_noop, - .deinit_peripherals = sim_noop, -}; - -/* ---- Scriptable per-slot verification results ---- */ - -static int parse_result[2]; -static int integrity_result[2]; -static int signature_result[2]; -static uint32_t slot_version[2]; - -/** Map a flash address back to the slot that starts there, or -1. */ -static int slot_index(uint32_t addr) +uint32_t eos_hal_slot_size(eos_slot_t slot) { - if (addr == SLOT_A_ADDR) return EOS_SLOT_A; - if (addr == SLOT_B_ADDR) return EOS_SLOT_B; - return -1; + return slot <= EOS_SLOT_B ? SLOT_SIZE : 0; } -/* ---- Image verification mocks (override eboot_core's real ones) ---- - * - * verify_slot() passes the parsed header straight to the integrity and - * signature checks, so the mocks stash the slot index in reserved[0] on - * parse and read it back to decide which scripted result to return. */ +int eos_hal_flash_erase(uint32_t addr, size_t len) +{ + erased_addr = addr; + erased_size = len; + return erase_result; +} int eos_image_parse_header(uint32_t addr, eos_image_header_t *out) { @@ -150,57 +108,20 @@ int eos_image_verify_signature(const eos_image_header_t *hdr) return signature_result[hdr->reserved[0]]; } -/* ---- Test harness ---- */ - -static int tests_run = 0; -static int tests_passed = 0; - -#define TEST(name) \ - static void name(void); \ - static void run_##name(void) { \ - memset(sim_flash, 0xFF, sizeof(sim_flash)); \ - sim_tick = 0; \ - for (int i = 0; i < 2; i++) { \ - parse_result[i] = EOS_ERR_NO_IMAGE; \ - integrity_result[i] = EOS_OK; \ - signature_result[i] = EOS_OK; \ - slot_version[i] = 0; \ - } \ - erase_result = EOS_OK; \ - erased_addr = 0; \ - erased_size = 0; \ - eos_hal_init(&sim_ops); \ - printf(" %-55s ", #name); \ - name(); \ - tests_passed++; \ - printf("[PASS]\n"); \ - } \ - static void name(void) - -#define ASSERT(cond) do { \ - if (!(cond)) { \ - printf("[FAIL] %s:%d: %s\n", __FILE__, __LINE__, #cond); \ - exit(1); \ - } \ -} while(0) - -/** Script a slot so that all three verification stages succeed. */ static void make_valid(eos_slot_t slot, uint32_t version) { parse_result[slot] = EOS_OK; slot_version[slot] = version; } -/* ---- Tests ---- */ - -TEST(test_scan_no_valid_slots) +static void test_scan_no_valid_slots(void) { ASSERT(eos_slot_scan_all() == 0); ASSERT(eos_slot_get_state(EOS_SLOT_A) == EOS_SLOT_STATE_EMPTY); ASSERT(eos_slot_get_state(EOS_SLOT_B) == EOS_SLOT_STATE_EMPTY); } -TEST(test_scan_one_valid_slot) +static void test_scan_one_valid_slot(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 2, 3)); @@ -214,7 +135,7 @@ TEST(test_scan_one_valid_slot) ASSERT(header.image_version == EOS_VERSION_MAKE(1, 2, 3)); } -TEST(test_scan_two_valid_slots) +static void test_scan_two_valid_slots(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); make_valid(EOS_SLOT_B, EOS_VERSION_MAKE(2, 0, 0)); @@ -225,7 +146,7 @@ TEST(test_scan_two_valid_slots) ASSERT(eos_slot_get_version(EOS_SLOT_B) == EOS_VERSION_MAKE(2, 0, 0)); } -TEST(test_verification_failures_are_invalid) +static void test_verification_failures_are_invalid(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); make_valid(EOS_SLOT_B, EOS_VERSION_MAKE(2, 0, 0)); @@ -238,7 +159,7 @@ TEST(test_verification_failures_are_invalid) ASSERT(eos_slot_get_header(EOS_SLOT_A, NULL) == EOS_ERR_INVALID); } -TEST(test_invalid_slot_is_rejected) +static void test_invalid_slot_is_rejected(void) { ASSERT(!eos_slot_is_valid(EOS_SLOT_RECOVERY)); ASSERT(eos_slot_get_state(EOS_SLOT_RECOVERY) == EOS_SLOT_STATE_EMPTY); @@ -247,7 +168,7 @@ TEST(test_invalid_slot_is_rejected) ASSERT(eos_slot_erase(EOS_SLOT_RECOVERY) == EOS_ERR_INVALID); } -TEST(test_erase_updates_state_only_on_success) +static void test_erase_updates_state_only_on_success(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); ASSERT(eos_slot_scan_all() == 1); @@ -264,12 +185,13 @@ TEST(test_erase_updates_state_only_on_success) ASSERT(eos_slot_get_version(EOS_SLOT_A) == 0); } + /* The boot-attempt counter is what makes an unproven image fall back instead * of bricking the device: mark_booting() has to increment it, confirm() has to * clear it, and needs_rollback() has to fire once it reaches max_attempts. * PR #37 added this behaviour but its test never compiled, so none of it was * ever exercised. */ -TEST(test_boot_attempts_drive_rollback) +static void test_boot_attempts_drive_rollback(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); ASSERT(eos_slot_scan_all() == 1); @@ -295,7 +217,7 @@ TEST(test_boot_attempts_drive_rollback) ASSERT(eos_slot_get_state(EOS_SLOT_A) == EOS_SLOT_STATE_CONFIRMED); } -TEST(test_boot_attempts_reject_invalid_slot) +static void test_boot_attempts_reject_invalid_slot(void) { ASSERT(eos_slot_mark_booting(EOS_SLOT_RECOVERY) == EOS_ERR_INVALID); ASSERT(eos_slot_confirm(EOS_SLOT_RECOVERY) == EOS_ERR_INVALID); @@ -311,7 +233,7 @@ TEST(test_boot_attempts_reject_invalid_slot) /* Erasing a slot must also drop its boot-attempt count; otherwise a freshly * flashed image inherits the failures of the one it replaced. */ -TEST(test_erase_resets_boot_attempts) +static void test_erase_resets_boot_attempts(void) { make_valid(EOS_SLOT_A, EOS_VERSION_MAKE(1, 0, 0)); ASSERT(eos_slot_scan_all() == 1); @@ -328,17 +250,16 @@ TEST(test_erase_resets_boot_attempts) int main(void) { - printf("=== eBootloader Slot Manager Tests ===\n\n"); - run_test_scan_no_valid_slots(); - run_test_scan_one_valid_slot(); - run_test_scan_two_valid_slots(); - run_test_verification_failures_are_invalid(); - run_test_invalid_slot_is_rejected(); - run_test_erase_updates_state_only_on_success(); - run_test_boot_attempts_drive_rollback(); - run_test_boot_attempts_reject_invalid_slot(); - run_test_erase_resets_boot_attempts(); - tests_run = 9; - printf("\n%d/%d tests passed\n", tests_passed, tests_run); - return (tests_passed == tests_run) ? 0 : 1; + printf("=== eBootloader Slot Manager Tests ===\n"); + RUN(test_scan_no_valid_slots); + RUN(test_scan_one_valid_slot); + RUN(test_scan_two_valid_slots); + RUN(test_verification_failures_are_invalid); + RUN(test_invalid_slot_is_rejected); + RUN(test_erase_updates_state_only_on_success); + RUN(test_boot_attempts_drive_rollback); + RUN(test_boot_attempts_reject_invalid_slot); + RUN(test_erase_resets_boot_attempts); + printf("\n%d/9 tests passed\n", tests_passed); + return 0; }