extern "C" void meshRegulatorCleanReset() __attribute__((weak)); #include #include #include #include #include #include #include #include #include #include #include "test_ota/mota_vectors.h" using namespace mesh::ota; namespace mesh { namespace ota { // Only the physical store is adapted. Parsing, context/CLI admission and // reboot dispatch below come from production. The internal applier includes // its real entry through the first read-rejection boundary; later flash and // cryptographic work is an explicit stub, not a simulated second applier. static unsigned store_calls = 0, metadata_calls = 0, apply_calls = 0; static bool apply_ok = true; struct StoreBytes { std::vector bytes{MOTA_VEC, MOTA_VEC + MOTA_VEC_LEN}; mutable unsigned reads = 0; unsigned fail_on_read = 0; bool hybrid = false; uint32_t staged_size() const { ++store_calls; return bytes.size(); } bool read(uint32_t off, uint8_t* dest, uint32_t n) const { ++store_calls; if (++reads == fail_on_read) return false; assert(off <= bytes.size() && n <= bytes.size() - off); memcpy(dest, bytes.data() + off, n); return true; } const uint8_t* data() const { ++store_calls; return bytes.data(); } bool is_hybrid() const { ++store_calls; return hybrid; } bool publish_hybrid_handoff() { return true; } }; struct OtaStoreFlashNrf52 : StoreBytes {}; struct OtaStoreQspiNrf52 : StoreBytes {}; struct OtaStoreSdNrf52 : StoreBytes {}; static OtaStoreFlashNrf52* g_nrf52_apply_store = nullptr; static uint8_t g_nrf52_external_handoff = GPREGRET2_OTA_STAGE_QSPI; static unsigned resets = 0; static uint8_t emitted_request = 0, emitted_source = 0; static bool softdevice_enabled = false; struct PowerRegisters { uint32_t GPREGRET = 0, GPREGRET2 = 0; } power_registers; #define NRF_POWER (&power_registers) namespace mesh_nrf52 { bool softdeviceIsEnabled(uint8_t& enabled) { enabled = softdevice_enabled; return true; } } void sd_power_gpregret_clr(uint32_t index, uint32_t bits) { (index ? power_registers.GPREGRET2 : power_registers.GPREGRET) &= ~bits; } void sd_power_gpregret_set(uint32_t index, uint32_t bits) { (index ? power_registers.GPREGRET2 : power_registers.GPREGRET) |= bits; } bool ota_nrf52_clear_reset_reasons() { return true; } uint32_t probe_ceiling() { return MOTA_NRF52_STAGE_CEILING_EXPANDED; } void ota_nrf52_set_reset_handoff(uint8_t request, uint8_t source) { power_registers.GPREGRET = request; power_registers.GPREGRET2 = source; } void NVIC_SystemReset() { emitted_request = power_registers.GPREGRET; emitted_source = power_registers.GPREGRET2; ++resets; } static bool boundaryApply(ApplyState& st, char* msg); @ENTRY@ static bool boundaryApply(ApplyState& st, char* msg) { ++apply_calls; if (!apply_ok) { strcpy(msg, "injected verification failure"); return false; } st.manifest_ok = st.slot_ok = true; strcpy(msg, "verified"); return true; } bool ota_apply_mota_nrf52(OtaStoreQspiNrf52&, const SignerAllowlist&, ApplyState& st, char* msg) { g_nrf52_apply_store = nullptr; if (!boundaryApply(st, msg)) return false; g_nrf52_external_handoff = GPREGRET2_OTA_STAGE_QSPI; return true; } bool ota_apply_mota_nrf52(OtaStoreSdNrf52&, const SignerAllowlist&, ApplyState& st, char* msg) { g_nrf52_apply_store = nullptr; if (!boundaryApply(st, msg)) return false; g_nrf52_external_handoff = GPREGRET2_OTA_STAGE_SD; return true; } bool ota_rescue_mota_nrf52(const uint8_t*, uint32_t, const SignerAllowlist&, const uint8_t*, uint32_t, ApplyState& st, char* msg) { return boundaryApply(st, msg); } bool ota_prepare_bootloader_update_nrf52(OtaStoreFlashNrf52&, const SignerAllowlist&, const OtaBootloaderIdentity&, const uint8_t*, const uint8_t*, const uint8_t*, ApplyState& st, char* msg) { return boundaryApply(st, msg); } #define ota_nrf52_effective_stage_ceiling probe_ceiling @REBOOT@ #undef ota_nrf52_effective_stage_ceiling @BOOT_REBOOT@ bool ota_self_firmware(SelfFwInfo& info) { ++metadata_calls; info.valid = true; info.fw_version = 1; return true; } struct OtaManager { enum FetchState { IDLE, COMPLETE }; FetchState fetchState() const { ++metadata_calls; return COMPLETE; } unsigned blocksHave() const { return 1; } unsigned blocksTotal() const { return 1; } const uint8_t* fetchManifestId() const { static uint8_t mid[4] = {1}; ++metadata_calls; return mid; } uint32_t target() const { return 1; } }; struct AdaptiveStore { OtaStoreFlashNrf52 internal; OtaStoreQspiNrf52 external; OtaStoreSdNrf52 sd; uint8_t mode = 0; // 0 internal, 1 hybrid, 2 QSPI, 3 SD bool usesExternal() const { ++store_calls; return mode >= 2; } bool usesQspi() const { ++store_calls; return mode == 2; } bool usesSd() const { ++store_calls; return mode == 3; } bool usesInternal() const { ++store_calls; return mode <= 1; } const char* selectionReason() const { return "internal"; } StoreBytes& active() { return mode <= 1 ? static_cast(internal) : mode == 2 ? static_cast(external) : static_cast(sd); } const StoreBytes& active() const { return const_cast(this)->active(); } uint32_t staged_size() const { return active().staged_size(); } bool read(uint32_t off, uint8_t* dest, uint32_t n) const { return active().read(off, dest, n); } OtaStoreFlashNrf52& internalStore() { ++store_calls; return internal; } OtaStoreQspiNrf52& externalStore() { ++store_calls; return external; } OtaStoreSdNrf52& sdStore() { ++store_calls; return sd; } }; struct Context { #if defined(OTA_RAK_AUTO_STORE) AdaptiveStore fetch_store; #else OtaStoreFlashNrf52 fetch_store; #endif OtaManager manager; SignerAllowlist allow; ApplyState apply_st; bool fetch_to_folder = false, apply_pending = false, bootloader_apply_pending = false; uint32_t apply_at = 0, apply_hard = 0; char hw_id[33] = "TESTHW"; bool sdCacheFetching() const { return false; } const OtaBootloaderIdentity& bootloaderIdentity() const { static OtaBootloaderIdentity identity; ++metadata_calls; return identity; } @METHODS@ }; @IS_CMD@ char fstate_char(OtaManager::FetchState) { return 'C'; } bool install(const char* a, char* reply, Context& c) { const char* rest = nullptr; @INSTALL@ return true; } } } static StoreBytes& selectMode(Context& c, uint8_t mode) { #if defined(OTA_RAK_AUTO_STORE) c.fetch_store.mode = mode; c.fetch_store.internal.hybrid = mode == 1; return c.fetch_store.active(); #else assert(mode < 2); c.fetch_store.hybrid = mode == 1; return c.fetch_store; #endif } static OtaStoreFlashNrf52* internalPointer(Context& c) { #if defined(OTA_RAK_AUTO_STORE) return &c.fetch_store.internal; #else return &c.fetch_store; #endif } static uint8_t sourceFor(uint8_t mode) { return mode == 0 ? GPREGRET2_OTA_STAGE_EXPANDED : mode == 1 ? GPREGRET2_OTA_STAGE_HYBRID : mode == 2 ? GPREGRET2_OTA_STAGE_QSPI : GPREGRET2_OTA_STAGE_SD; } static void assertApplyState(const ApplyState& actual, const ApplyState& expected) { assert(actual.manifest_ok == expected.manifest_ok && actual.sig_ok == expected.sig_ok); assert(actual.trusted == expected.trusted && actual.slot_ok == expected.slot_ok); assert(actual.slot_addr == expected.slot_addr && actual.slot_size == expected.slot_size); assert(actual.image_size == expected.image_size); assert(memcmp(actual.image_hash, expected.image_hash, sizeof(actual.image_hash)) == 0); } static void invoke(Context& c, unsigned entry, char* reply) { uint8_t hash[8] = {1}, mid[4] = {1}; bool result = false; switch (entry) { case 0: result = c.apply_fetched(reply); break; case 1: result = c.apply_fetched_trusted_auto(reply); break; case 2: result = c.apply_fetched_rescue(hash, reply); break; case 3: result = c.apply_fetched_impl(nullptr, false, reply); break; case 4: result = c.apply_fetched_bootloader(mid, hash, reply); break; default: assert(install(entry == 5 ? "install" : entry == 6 ? "apply" : "applydelta", reply, c)); assert(strncmp(reply, "ERR", 3) == 0); } assert(!result); } int main() { #if defined(OTA_RAK15002_SD_STORE) const unsigned modes = 4; #elif defined(OTA_RAK_AUTO_STORE) const unsigned modes = 3; #else const unsigned modes = 2; #endif unsigned pending_cases = 0, healthy_cases = 0; for (uint8_t mode = 0; mode < modes; ++mode) { for (unsigned pending : {1U, 2U, 3U}) for (bool folder : {false, true}) for (unsigned entry = 0; entry < 8; ++entry) { Context c; StoreBytes& store = selectMode(c, mode); store.fail_on_read = 3; // would reach/reset the applier handoff if admitted c.apply_pending = (pending & 1) != 0; c.bootloader_apply_pending = (pending & 2) != 0; c.fetch_to_folder = folder; c.apply_at = 0x12345678U; c.apply_hard = 0x22334455U; c.apply_st.manifest_ok = c.apply_st.sig_ok = c.apply_st.trusted = c.apply_st.slot_ok = true; c.apply_st.slot_addr = 0x26000; c.apply_st.slot_size = 0xB8000; c.apply_st.image_size = 12345; memset(c.apply_st.image_hash, 0xA5, sizeof(c.apply_st.image_hash)); const ApplyState before = c.apply_st; // Seed the state left by an already-approved handoff. Initial signature // verification is deliberately not claimed by this host admission test. g_nrf52_apply_store = mode < 2 ? internalPointer(c) : nullptr; g_nrf52_external_handoff = sourceFor(mode); OtaStoreFlashNrf52* pointer = g_nrf52_apply_store; const uint8_t external = g_nrf52_external_handoff; store_calls = metadata_calls = apply_calls = 0; char reply[160] = {}; invoke(c, entry, reply); #if defined(OTA_INTERNAL_BOOTLOADER_UPDATE) if (entry == 2) assert(strstr(reply, "rescue is disabled") != nullptr); else #endif assert(strstr(reply, "already armed") != nullptr); assert(store_calls == 0 && metadata_calls == 0 && apply_calls == 0); assert(store.reads == 0); assert(c.apply_pending == ((pending & 1) != 0)); assert(c.bootloader_apply_pending == ((pending & 2) != 0)); assert(c.fetch_to_folder == folder && c.apply_at == 0x12345678U && c.apply_hard == 0x22334455U); assertApplyState(c.apply_st, before); assert(g_nrf52_apply_store == pointer && g_nrf52_external_handoff == external); // Execute the appropriate production reboot function with register and // SoftDevice adapters; no reset or peripheral access reaches hardware. for (bool enabled : {false, true}) { softdevice_enabled = enabled; const unsigned previous_resets = resets; if (c.bootloader_apply_pending) { ota_reboot_to_bootloader_update(); assert(emitted_request == GPREGRET_OTA_BOOTLOADER_UPDATE); #if defined(OTA_INTERNAL_BOOTLOADER_UPDATE) assert(emitted_source == GPREGRET2_OTA_STAGE_EXPANDED); #else assert(emitted_source == GPREGRET2_OTA_STAGE_QSPI); #endif } else { ota_reboot_to_apply(); assert(emitted_request == GPREGRET_OTA_APPLY && emitted_source == sourceFor(mode)); } assert(resets == previous_resets + 1); } ++pending_cases; } // A failed first application is not a latched armed state. Preserve normal // preflight rejection and let an explicit retry reach the apply boundary. for (unsigned fault : {0U, 1U, 2U, 3U, 4U}) { Context c; StoreBytes& store = selectMode(c, mode); g_nrf52_apply_store = nullptr; store_calls = metadata_calls = apply_calls = 0; apply_ok = fault != 3; char reply[160] = {}; if (fault == 0) strcpy(c.hw_id, "WRONG"); if (fault == 1) store.fail_on_read = 1; if (fault == 2) c.fetch_to_folder = true; if (fault == 4) assert(!c.apply_fetched_trusted_auto(reply)); // unsigned auto refusal else assert(!c.apply_fetched(reply)); assert(!c.apply_pending && !c.bootloader_apply_pending); assert(apply_calls == (fault == 3 ? 1U : 0U)); strcpy(c.hw_id, "TESTHW"); store.fail_on_read = 0; c.fetch_to_folder = false; apply_ok = true; assert(c.apply_fetched(reply)); assert(c.apply_pending && !c.bootloader_apply_pending); assert(c.apply_st.manifest_ok && c.apply_st.slot_ok); ++healthy_cases; } { Context c; StoreBytes& store = selectMode(c, mode); store.bytes[9] |= MFLAG_SIGNED; char reply[160] = {}; // Actual auto preflight requires signed/newer; downstream cryptographic // acceptance remains the explicit boundary stub, not a signature test. assert(c.apply_fetched_trusted_auto(reply)); assert(c.apply_pending && !c.bootloader_apply_pending); ++healthy_cases; } } #if !defined(OTA_INTERNAL_BOOTLOADER_UPDATE) && !defined(OTA_SD_STORE) { Context c; selectMode(c, 0); uint8_t hash[8] = {1}; char reply[160] = {}; apply_ok = false; assert(!c.apply_fetched_rescue(hash, reply)); assert(!c.apply_pending); apply_ok = true; assert(c.apply_fetched_rescue(hash, reply)); assert(c.apply_pending && !c.bootloader_apply_pending); ++healthy_cases; } #endif #if defined(OTA_INTERNAL_BOOTLOADER_UPDATE) // Privileged first-apply admission remains usable; failed verification does // not arm either flag, and retry may arm bootloader-specific deferred reboot. { Context c; selectMode(c, 0); uint8_t mid[4] = {1}, hash[8] = {1}; char reply[160] = {}; apply_ok = false; assert(!c.apply_fetched_bootloader(mid, hash, reply)); assert(!c.apply_pending && !c.bootloader_apply_pending); apply_ok = true; assert(c.apply_fetched_bootloader(mid, hash, reply)); assert(c.apply_pending && c.bootloader_apply_pending); ++healthy_cases; } #endif printf("PASS: %u armed admission cases; %u first-apply/retry controls\n", pending_cases, healthy_cases); }