mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 15:01:58 +00:00
185 lines
7.2 KiB
Python
185 lines
7.2 KiB
Python
"""Exercise the actual adaptive store with simulated internal and QSPI media."""
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class AdaptiveBootloaderTest(unittest.TestCase):
|
|
def test_cli_uses_application_storage_gate_only_for_applications(self):
|
|
cli = (ROOT / "src/helpers/ota/OtaCli.cpp").read_text()
|
|
error = cli.index('ERR bootloader cannot apply from detected QSPI storage')
|
|
start = cli.rfind('#elif defined(OTA_RAK_AUTO_STORE)', 0, error)
|
|
end = cli.index('#elif defined(OTA_QSPI_STORE)', error)
|
|
gate = cli[cli.index('\n', start) + 1:end]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <cstdint>
|
|
constexpr uint8_t OTA_BL_STORAGE_QSPI = 4;
|
|
struct Store {
|
|
int mode;
|
|
bool usesExternal() { return mode == 1; }
|
|
bool usesInternal() { return mode == 0; }
|
|
const char* selectionReason() { return "unsafe wiring"; }
|
|
};
|
|
bool rejected(bool selboot, int mode, uint8_t flags) {
|
|
struct { Store fetch_store; } c = {{mode}};
|
|
struct { uint8_t storage_flags; } bl = {flags};
|
|
char reply[160];
|
|
''' + gate + r'''
|
|
return false;
|
|
}
|
|
int main() {
|
|
// The CLI has already required exact identity and privileged boot caps.
|
|
assert(!rejected(true, 1, 0x0A)); // canonical bootloader, external app store
|
|
assert(!rejected(true, 0, 0x0A));
|
|
assert(!rejected(true, 2, 0x0A)); // optional NOR is irrelevant to internal boot staging
|
|
assert(rejected(false, 1, 0x0A)); // app still needs the external capability
|
|
assert(rejected(false, 2, 0x1E)); // unsafe application store remains rejected
|
|
assert(!rejected(false, 1, 0x1E));
|
|
assert(!rejected(false, 0, 0x0A));
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory)
|
|
(path / "cli.cpp").write_text(source)
|
|
subprocess.run(["g++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
str(path / "cli.cpp"), "-o", str(path / "cli")], check=True)
|
|
subprocess.run([str(path / "cli")], check=True)
|
|
|
|
def test_storage_switch_and_manual_resume(self):
|
|
header = (ROOT / "src/helpers/ota/OtaStoreAdaptiveNrf52.h").read_text()
|
|
body = header[header.index("class OtaStoreAdaptiveNrf52"):
|
|
header.index("} // namespace ota")]
|
|
source = r'''
|
|
#include <cassert>
|
|
#include <new>
|
|
#include "src/helpers/ota/OtaStore.h"
|
|
#include "src/helpers/ota/OtaBlInfo.h"
|
|
#include "src/helpers/ota/OtaRakStoragePolicy.h"
|
|
using namespace mesh::ota;
|
|
namespace mesh { namespace ota {
|
|
static bool capable = true, boot_resume = false, app_resume = false;
|
|
static unsigned detected = 2, internal_begins = 0, external_begins = 0;
|
|
static unsigned internal_live = 0, external_live = 0;
|
|
struct OtaBootloaderIdentity { bool crc_ok = true; const char* device_name = "4631_DFU"; };
|
|
bool ota_installed_bootloader_identity(OtaBootloaderIdentity&) { return true; }
|
|
struct MotaManifest { bool is_bootloader() const { return boot_resume; } };
|
|
bool mota_parse_manifest(const uint8_t*, size_t, MotaManifest&) { return true; }
|
|
OtaBlCaps simulatedCaps() {
|
|
OtaBlCaps c; c.present = true; c.apply_abi = capable ? 3 : 2;
|
|
c.codec_mask = 5; c.storage_flags = capable ? 0x1e : 0x16;
|
|
c.optional_app_storage = capable ? 0x14 : 0; return c;
|
|
}
|
|
OtaBlCaps simulatedBootCaps() {
|
|
auto c = simulatedCaps(); c.storage_flags = capable ? 0x0a : 0x02;
|
|
c.optional_app_storage = 0; return c;
|
|
}
|
|
class Store : public OtaStore {
|
|
public:
|
|
uint32_t size = 0;
|
|
bool begin(uint32_t n) override { size = n; return true; }
|
|
bool write(uint32_t, const uint8_t*, uint32_t) override { return true; }
|
|
bool read(uint32_t, uint8_t*, uint32_t) const override { return true; }
|
|
uint32_t capacity() const override { return 65536; }
|
|
uint32_t staged_size() const override { return size; }
|
|
void clear() override { size = 0; }
|
|
};
|
|
class OtaStoreFlashNrf52 : public Store {
|
|
public:
|
|
OtaStoreFlashNrf52() { ++internal_live; assert(external_live == 0); }
|
|
~OtaStoreFlashNrf52() { --internal_live; }
|
|
bool begin(uint32_t n) override { ++internal_begins; return Store::begin(n); }
|
|
bool reopenFor(const uint8_t*, uint32_t) override { return boot_resume; }
|
|
};
|
|
class OtaStoreQspiNrf52 : public Store {
|
|
public:
|
|
OtaStoreQspiNrf52() { ++external_live; assert(internal_live == 0); }
|
|
~OtaStoreQspiNrf52() { --external_live; }
|
|
static uint8_t autoDetect() { return detected; }
|
|
static bool headerW25Detected() { return true; }
|
|
uint32_t jedec_id() const { return 0xef4015; }
|
|
uint8_t status1() const { return 0; }
|
|
const char* last_stage() const { return "ok"; }
|
|
const char* last_error() const { return "ok"; }
|
|
bool begin(uint32_t n) override { ++external_begins; return Store::begin(n); }
|
|
bool reopenFor(const uint8_t*, uint32_t) override { return app_resume; }
|
|
};
|
|
#define ota_bootloader_app_caps simulatedCaps
|
|
#define ota_bootloader_update_caps simulatedBootCaps
|
|
'''
|
|
source += body
|
|
source += r'''
|
|
} }
|
|
int main() {
|
|
uint8_t mid[4] = {1, 2, 3, 4};
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(store.usesExternal());
|
|
assert(store.plan_layout(true, 40960, 365, 40960, true));
|
|
assert(store.usesInternal());
|
|
assert(store.begin(41330));
|
|
assert(internal_begins == 1 && external_begins == 0);
|
|
assert(store.plan_layout(true, 100000, 365, 100000, false));
|
|
assert(store.usesExternal());
|
|
assert(store.begin(100370));
|
|
assert(external_begins == 1);
|
|
boot_resume = true;
|
|
assert(!store.reopenFor(nullptr, 0));
|
|
assert(store.usesExternal());
|
|
assert(store.reopenFor(mid, 0));
|
|
assert(store.usesInternal());
|
|
boot_resume = false;
|
|
assert(!store.reopenFor(mid, 0));
|
|
assert(store.usesExternal());
|
|
app_resume = true;
|
|
assert(store.reopenFor(mid, 0));
|
|
assert(store.usesExternal());
|
|
capable = false;
|
|
assert(!store.plan_layout(true, 40960, 365, 40960, true));
|
|
assert(store.usesExternal());
|
|
}
|
|
assert(internal_live == 0 && external_live == 0);
|
|
capable = true; detected = 0;
|
|
{
|
|
OtaStoreAdaptiveNrf52 store;
|
|
assert(store.usesInternal());
|
|
assert(store.plan_layout(true, 40960, 365, 40960, true));
|
|
assert(store.begin(41330));
|
|
assert(store.plan_layout(false, 40960, 365, 40960, false));
|
|
assert(store.usesInternal());
|
|
}
|
|
assert(internal_live == 0 && external_live == 0);
|
|
for (uint8_t flags : {uint8_t(0x0a), uint8_t(0x1e)}) {
|
|
OtaBlCaps c = simulatedCaps(); c.storage_flags = flags;
|
|
assert(ota_bootloader_self_update_caps_valid(c));
|
|
c.apply_abi = 2; assert(!ota_bootloader_self_update_caps_valid(c));
|
|
}
|
|
for (uint8_t flags : {uint8_t(0x09), uint8_t(0x0e), uint8_t(0x16), uint8_t(0x1f)}) {
|
|
OtaBlCaps c = simulatedCaps(); c.storage_flags = flags;
|
|
assert(!ota_bootloader_self_update_caps_valid(c));
|
|
}
|
|
}
|
|
'''
|
|
source = "#include <initializer_list>\n" + source
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cpp = Path(directory) / "adaptive.cpp"
|
|
exe = Path(directory) / "adaptive"
|
|
cpp.write_text(source)
|
|
subprocess.run([
|
|
"c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-DOTA_RAK_AUTO_STORE", "-DOTA_INTERNAL_BOOTLOADER_UPDATE",
|
|
"-I", str(ROOT), str(cpp), "-o", str(exe),
|
|
], check=True)
|
|
subprocess.run([str(exe)], check=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|