Files
MeshCore/tools/mota/tests/migration_stubs/runtime.cpp
T

371 lines
13 KiB
C++

#include "Arduino.h"
#include "BootloaderMigrationResource.h"
#include "SHA256.h"
// The test app-base override bypasses the target linker's declarations.
namespace mesh { namespace ota {
extern "C" uint32_t __flash_arduino_end[];
extern "C" const uint8_t g_meshcore_internal_extrafs __attribute__((weak));
} }
#include "helpers/ota/OtaSelf.h"
#include "nrf.h"
#include "nrf_mbr.h"
#include "nrf_sdm.h"
#include "nrf_soc.h"
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <stdexcept>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/mman.h>
#endif
namespace migration_test {
uint32_t now = 0;
unsigned endf_checks = 0;
unsigned softdevice_checks = 0;
unsigned disables = 0;
unsigned resets = 0;
unsigned copies = 0;
bool candidate_installed = false;
void mbr_command() {
++copies;
throw std::runtime_error("unexpected MBR command in state-machine test");
}
} // namespace migration_test
// Include the actual driver. Only its target-specific naked SVC adapter is
// replaced: function-like 'volatile(...)' captures the extended assembly, but
// leaves ordinary 'volatile uint8_t *' memory reads unchanged. The adapter is
// an explicit failing trap, never a successful simulated flash operation.
#define __attribute__(...)
#define __asm
#define volatile(...) migration_test::mbr_command()
#include "helpers/ota/BootloaderMigration.cpp"
#undef volatile
#undef __asm
#undef __attribute__
const uint8_t migration_candidate[40960] = {};
const uint8_t migration_candidate_sha256[32] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
const uint8_t migration_legacy_sha256[32] = {};
const bool migration_has_legacy = false;
const uint32_t migration_app_base = 0x26000, migration_boot_version = 0x02040CFF,
migration_board_id = 0x239A0029;
const uint16_t migration_softdevice_fwid = 0xB6;
const char migration_device_name[16] = "4631_DFU";
namespace mesh { namespace ota {
extern "C" {
uint32_t __flash_arduino_start[1] = {};
uint32_t __flash_arduino_end[1] = {};
}
} }
MigrationTestPower power_registers = {};
MigrationTestPower *NRF_POWER = &power_registers;
MigrationTestFicr *NRF_FICR = nullptr;
MigrationTestAcl *NRF_ACL = nullptr;
MigrationTestUicr *NRF_UICR = nullptr;
MigrationTestWdt *NRF_WDT = nullptr;
uint32_t millis() {
return migration_test::now;
}
void NVIC_SystemReset() {
++migration_test::resets;
throw std::runtime_error("unexpected reset in state-machine test");
}
uint32_t sd_softdevice_is_enabled(uint8_t *enabled) {
++migration_test::softdevice_checks;
*enabled = 0;
return NRF_SUCCESS;
}
uint32_t sd_softdevice_disable() {
++migration_test::disables;
throw std::runtime_error("unexpected SoftDevice disable in state-machine test");
}
uint32_t sd_power_gpregret_get(uint32_t, uint32_t *result) {
*result = 0;
return NRF_SUCCESS;
}
void SHA256::reset() {
}
void SHA256::update(const void *, size_t) {
}
void SHA256::finalize(void *out, size_t size) {
assert(size == 32);
// Scheduling tests exercise the full memory-read loop; cryptography itself
// is the production SHA256 library's contract, not this hardware stub.
std::memset(out, 0, size);
if (migration_test::candidate_installed)
std::memcpy(out, migration_candidate_sha256, size);
}
namespace mesh {
namespace ota {
bool ota_self_firmware(SelfFwInfo &) {
++migration_test::endf_checks;
return false; // Stop at the first install preflight, before hardware access.
}
} // namespace ota
} // namespace mesh
namespace {
using namespace mesh::ota;
const char confirmation[] = "0123456789ABCDEF";
char reply[160];
void wr16(uint8_t *p, uint16_t value) {
p[0] = value;
p[1] = value >> 8;
}
void wr32(uint8_t *p, uint32_t value) {
wr16(p, value);
wr16(p + 2, value >> 16);
}
uint8_t *installed_manifest(uint32_t version) {
uint8_t *boot = reinterpret_cast<uint8_t *>(migration::BOOT_BASE);
std::memset(boot, 0xFF, migration::BOOT_SIZE);
uint8_t *p = boot + OTA_BOOT_CANDIDATE_MANIFEST_OFFSET;
wr32(p, OTA_BOOT_MANIFEST_MAGIC0);
wr32(p + 4, OTA_BOOT_MANIFEST_MAGIC1);
wr16(p + 8, 1);
wr16(p + 10, 44);
wr32(p + 12, migration::BOOT_BASE);
wr32(p + 16, migration::BOOT_SIZE);
wr32(p + 20, migration_board_id);
std::memcpy(p + 24, migration_device_name, 16);
wr32(p + 44, OTA_BOOT_CONTINUITY_MAGIC0);
wr32(p + 48, OTA_BOOT_CONTINUITY_MAGIC1);
wr16(p + 52, 2);
wr16(p + 54, 32);
wr32(p + 56, version);
wr16(p + 60, 140);
wr16(p + 62, migration_softdevice_fwid);
wr32(p + 64, migration_app_base);
wr16(p + 68, 1);
wr16(p + 70, 0);
wr32(p + 72, 0);
return p;
}
void seal_manifest(uint8_t *p) {
const uint8_t *boot = reinterpret_cast<const uint8_t *>(migration::BOOT_BASE);
wr32(p + 40, ota_boot_image_crc32(boot, migration::BOOT_SIZE,
OTA_BOOT_CANDIDATE_MANIFEST_OFFSET + 40));
}
void arm() {
assert(bootloader_migration_request(confirmation, reply, sizeof(reply)));
assert(bootloader_migration_pending());
assert(std::strstr(reply, "OK migration armed"));
}
void no_install() {
assert(migration_test::endf_checks == 0);
assert(migration_test::softdevice_checks == 0);
assert(migration_test::disables == 0);
assert(migration_test::resets == 0);
assert(migration_test::copies == 0);
}
void status_result(unsigned result) {
bootloader_migration_status(reply, sizeof(reply));
char expected[24];
std::snprintf(expected, sizeof(expected), "last=%u", result);
assert(std::strstr(reply, expected));
}
} // namespace
int main(int argc, char **argv) {
assert(argc == 2);
using namespace migration_test;
#if defined(_WIN32)
assert(VirtualAlloc(reinterpret_cast<void *>(0xF0000), 0x10000,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE) != nullptr);
#else
assert(mmap(reinterpret_cast<void *>(0xF0000), 0x10000, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) != MAP_FAILED);
#endif
const char *scenario = argv[1];
if (std::strcmp(scenario, "already-installed") == 0) {
candidate_installed = true;
assert(bootloader_migration_request(confirmation, reply, sizeof(reply)));
assert(!bootloader_migration_pending());
assert(std::strstr(reply, "already installed"));
now = 2000;
bootloader_migration_tick(true, true);
no_install();
bootloader_migration_status(reply, sizeof(reply));
assert(std::strstr(reply, "already installed"));
} else if (std::strcmp(scenario, "same-version") == 0 ||
std::strcmp(scenario, "newer-version") == 0) {
const uint32_t version = std::strcmp(scenario, "same-version") == 0
? migration_boot_version : 0x02040D01;
seal_manifest(installed_manifest(version));
// Live status has to detect this before any request, as it would after reboot.
bootloader_migration_status(reply, sizeof(reply));
assert(std::strstr(reply, "up to date"));
assert(!bootloader_migration_pending());
assert(!bootloader_migration_request("0123456789ABCDE0", reply, sizeof(reply)));
assert(!bootloader_migration_pending());
softdevice_checks = 0; // Status may read the retained diagnostic result.
const char *tokens[] = {nullptr, confirmation};
for (const char *token : tokens) {
assert(bootloader_migration_request(token, reply, sizeof(reply)));
assert(!bootloader_migration_pending());
assert(std::strstr(reply, "up to date"));
now += 2000;
bootloader_migration_tick(true, true);
no_install();
}
} else if (std::strcmp(scenario, "easy-update") == 0 ||
std::strcmp(scenario, "older-preview") == 0) {
if (std::strcmp(scenario, "older-preview") == 0)
seal_manifest(installed_manifest(0x02040CFE));
assert(bootloader_migration_request(nullptr, reply, sizeof(reply)));
assert(bootloader_migration_pending());
assert(std::strstr(reply, "OK migration armed"));
now = 1499;
bootloader_migration_tick(true, true);
no_install();
++now;
bootloader_migration_tick(true, true);
assert(endf_checks == 1);
assert(!bootloader_migration_pending());
} else if (std::strncmp(scenario, "invalid-version-", 16) == 0) {
uint8_t *p = installed_manifest(0x02040DFF);
const char *mutation = scenario + 16;
if (std::strcmp(mutation, "board") == 0) wr32(p + 20, migration_board_id ^ 1);
else if (std::strcmp(mutation, "name") == 0) p[24] = 'X';
else if (std::strcmp(mutation, "family") == 0) wr16(p + 60, 132);
else if (std::strcmp(mutation, "fwid") == 0) wr16(p + 62, 0x123);
else if (std::strcmp(mutation, "app") == 0) wr32(p + 64, 0x27000);
else if (std::strcmp(mutation, "abi") == 0) wr16(p + 68, 2);
else if (std::strcmp(mutation, "continuity") == 0) std::memset(p + 44, 0xFF, 32);
else assert(std::strcmp(mutation, "crc") == 0);
seal_manifest(p);
if (std::strcmp(mutation, "crc") == 0) p[40] ^= 1;
bootloader_migration_status(reply, sizeof(reply));
assert(!std::strstr(reply, "up to date"));
assert(bootloader_migration_request(nullptr, reply, sizeof(reply)));
assert(bootloader_migration_pending());
assert(!std::strstr(reply, "up to date"));
now = 1500;
bootloader_migration_tick(true, true);
// The production install preflight still runs, and the EndF stub refuses it.
assert(endf_checks == 1);
assert(!bootloader_migration_pending());
} else if (std::strcmp(scenario, "wrong-confirmation") == 0) {
assert(!bootloader_migration_request("0123456789ABCDE0", reply, sizeof(reply)));
assert(!bootloader_migration_request("0123456789abcdef", reply, sizeof(reply)));
assert(!bootloader_migration_request("0123456789ABCDEF ", reply, sizeof(reply)));
assert(!bootloader_migration_pending());
now = 2000;
bootloader_migration_tick(true, true);
no_install();
} else if (std::strcmp(scenario, "grace-and-drain") == 0) {
arm();
now = 1499;
bootloader_migration_tick(true, true);
no_install();
now = 1500;
bootloader_migration_tick(false, true);
no_install();
now = 29999;
bootloader_migration_tick(false, true);
no_install();
assert(bootloader_migration_pending());
bootloader_migration_tick(true, true);
assert(endf_checks == 1);
assert(!bootloader_migration_pending());
assert(disables == 0 && resets == 0 && copies == 0);
status_result(1);
} else if (std::strcmp(scenario, "duplicate-arm") == 0) {
arm();
now = 1499;
assert(!bootloader_migration_request("0123456789ABCDE0", reply, sizeof(reply)));
assert(bootloader_migration_pending());
assert(bootloader_migration_request(nullptr, reply, sizeof(reply)));
assert(std::strstr(reply, "OK bootloader update already armed"));
assert(bootloader_migration_request(confirmation, reply, sizeof(reply)));
assert(std::strstr(reply, "already armed"));
now = 1500;
bootloader_migration_tick(true, true);
assert(endf_checks == 1); // A duplicate must not extend the grace period.
} else if (std::strcmp(scenario, "timeout") == 0) {
arm();
now = 30000;
bootloader_migration_tick(true, true);
assert(!bootloader_migration_pending());
no_install();
status_result(15);
bootloader_migration_tick(true, true);
assert(endf_checks == 0);
} else if (std::strcmp(scenario, "ota-cancel-rearm") == 0) {
arm();
now = 100;
bootloader_migration_tick(false, false);
assert(!bootloader_migration_pending());
no_install();
status_result(16);
now = 2000;
bootloader_migration_tick(true, true);
assert(endf_checks == 0); // Becoming idle must not revive cancelled work.
arm();
now = 3499;
bootloader_migration_tick(true, true);
assert(endf_checks == 0);
now = 3500;
bootloader_migration_tick(true, true);
assert(endf_checks == 1);
} else if (std::strcmp(scenario, "ota-cancel-at-install") == 0) {
arm();
now = 1500;
bootloader_migration_tick(true, false);
assert(!bootloader_migration_pending());
no_install();
status_result(16);
} else if (std::strcmp(scenario, "stale-retained-result") == 0) {
power_registers.GPREGRET = 0xCC;
status_result(12);
arm();
status_result(0);
now = 30000;
bootloader_migration_tick(true, true);
assert(!bootloader_migration_pending());
status_result(15);
arm();
status_result(0);
++now;
bootloader_migration_tick(true, false);
assert(!bootloader_migration_pending());
status_result(16);
assert(power_registers.GPREGRET == 0xCC);
assert(endf_checks == 0);
} else if (std::strcmp(scenario, "clock-wrap") == 0) {
now = UINT32_MAX - 999u;
arm();
now += 1499u;
bootloader_migration_tick(true, true);
no_install();
++now;
bootloader_migration_tick(true, true);
assert(endf_checks == 1);
} else {
throw std::runtime_error("unknown scenario");
}
assert(disables == 0 && resets == 0 && copies == 0);
}