mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-16 08:30:04 -04:00
installDefaultModuleConfig() memset sizeof(meshtastic_ModuleConfig) - the
368-byte union-backed wire oneof - over `moduleConfig`, which is a
meshtastic_LocalModuleConfig: 1092 bytes with every submessage inlined. The
function assigns only the fields it cares about and relies on that memset to
zero the rest, so every byte past offset 368 that it never assigns kept its
previous value across what is supposed to be a full reset.
installDefaultConfig() directly above already used the correct
sizeof(meshtastic_LocalConfig); only the module variant was wrong.
statusmessage is the field this shows up on. It sits at offset 609 and is
never assigned by the defaults installer, so it survives both routes into
installDefaultModuleConfig():
- moduleConfig.version < DEVICESTATE_MIN_VER -> "old, discard". The decode
succeeded, so the complete old config is in RAM and its statusmessage
survives the discard verbatim.
- loadProto() failure -> whatever a partial decode wrote there survives
(loadProto itself clears correctly, using the caller's objSize).
node_status is char[80]. When the surviving bytes carry no NUL, nanopb
refuses the field ("unterminated string"), pb_encode_to_bytes() returns 0 and
PhoneAPI::getFromRadio() returns 0. config_state has already advanced, so the
frame is never retried - and 0 is the client's end-of-data sentinel, so the
rest of the config dump goes with it and the client never receives
StatusMessageConfig.
traffic_management is not affected: installDefaultModuleConfig() calls
installTrafficManagementDefaults(), which reassigns the whole submessage and
its has_ flag regardless of the memset size.
Also add has_traffic_management to the has_* list in saveToDiskNoRetry() for
consistency - it was the only module config missing from it.
427 lines
18 KiB
C++
427 lines
18 KiB
C++
// NodeDB boot-recovery contract: an undecodable config.proto must freeze identity (no keygen, no
|
|
// overwrite), an absent one takes the fresh-install path, and a corrupt nodes.proto does neither.
|
|
// The tests are a ladder (state=per-suite): arrange /prefs, then "reboot" a fresh NodeDB.
|
|
#include "MeshTypes.h" // Include BEFORE TestUtil.h
|
|
#include "TestUtil.h"
|
|
#include <unity.h>
|
|
|
|
#if defined(ARCH_PORTDUINO)
|
|
#define NBR_TEST_ENTRY extern "C"
|
|
#else
|
|
#define NBR_TEST_ENTRY
|
|
#endif
|
|
|
|
#include "FSCommon.h" // defines FSCom; must precede the feature guard below
|
|
|
|
// The identity-freeze contract only exists where there is a filesystem and boot keygen.
|
|
#if defined(FSCom) && !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
|
|
|
#include "mesh/NodeDB.h"
|
|
#include "mesh/TypeConversions.h"
|
|
#include <ErriezCRC32.h>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
// Friend seam declared in NodeDB.h (PIO_UNIT_TESTING): read the private degraded-boot flag.
|
|
// Never instantiated - constructing one would run the real boot sequence.
|
|
class NodeDBTestShim : public NodeDB
|
|
{
|
|
public:
|
|
static bool decodeFailed(const NodeDB *db) { return db->configDecodeFailed; }
|
|
};
|
|
|
|
namespace
|
|
{
|
|
|
|
// --- Identity baseline captured after a healthy keyed boot ---
|
|
uint32_t baseNodeNum = 0;
|
|
uint8_t basePublicKey[32];
|
|
uint8_t basePrivateKey[32];
|
|
char baseLongName[sizeof(meshtastic_User::long_name)];
|
|
std::vector<uint8_t> goodConfigBytes; // byte-exact healthy config.proto for restore tests
|
|
|
|
// --- File helpers (through FSCom so the tests stay agnostic about the mountpoint) ---
|
|
|
|
bool readFileBytes(const char *path, std::vector<uint8_t> &out)
|
|
{
|
|
out.clear();
|
|
File f = FSCom.open(path, FILE_O_READ);
|
|
if (!f)
|
|
return false;
|
|
uint8_t buf[512];
|
|
size_t n;
|
|
while ((n = f.read(buf, sizeof(buf))) > 0)
|
|
out.insert(out.end(), buf, buf + n);
|
|
f.close();
|
|
return true;
|
|
}
|
|
|
|
void writeFileBytes(const char *path, const uint8_t *data, size_t len)
|
|
{
|
|
FSCom.remove(path); // FILE_O_WRITE is append on some backends; start clean
|
|
File f = FSCom.open(path, FILE_O_WRITE);
|
|
TEST_ASSERT_TRUE_MESSAGE(f, path);
|
|
TEST_ASSERT_EQUAL_size_t(len, f.write(data, len));
|
|
f.close();
|
|
}
|
|
|
|
// FNV-1a content fingerprint; answers only "did this file change?". 0 == missing file.
|
|
uint64_t fileFingerprint(const char *path)
|
|
{
|
|
std::vector<uint8_t> bytes;
|
|
if (!readFileBytes(path, bytes))
|
|
return 0;
|
|
uint64_t h = 1469598103934665603ULL;
|
|
for (uint8_t b : bytes) {
|
|
h ^= b;
|
|
h *= 1099511628211ULL;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
// A varint tag of five 0xFF bytes overflows 32 bits, so nanopb fails deterministically.
|
|
const uint8_t kGarbage[32] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
|
|
// --- Reboot helper ---
|
|
|
|
// A real boot starts with a zeroed nodeDatabase; in-process the global retains the previous
|
|
// boot's vector (the decode callback appends, it does not clear), so reset it first.
|
|
void rebootNodeDB()
|
|
{
|
|
nodeDatabase.version = 0;
|
|
nodeDatabase.nodes.clear();
|
|
NodeDB *rebooted = new NodeDB();
|
|
delete nodeDB;
|
|
nodeDB = rebooted;
|
|
}
|
|
|
|
void captureIdentityBaseline()
|
|
{
|
|
TEST_ASSERT_EQUAL(32, config.security.public_key.size);
|
|
TEST_ASSERT_EQUAL(32, config.security.private_key.size);
|
|
TEST_ASSERT_EQUAL(32, owner.public_key.size);
|
|
baseNodeNum = myNodeInfo.my_node_num;
|
|
memcpy(basePublicKey, config.security.public_key.bytes, 32);
|
|
memcpy(basePrivateKey, config.security.private_key.bytes, 32);
|
|
strncpy(baseLongName, owner.long_name, sizeof(baseLongName));
|
|
baseLongName[sizeof(baseLongName) - 1] = '\0';
|
|
TEST_ASSERT_TRUE(readFileBytes(configFileName, goodConfigBytes));
|
|
TEST_ASSERT_GREATER_THAN(1, goodConfigBytes.size());
|
|
}
|
|
|
|
// Persist a set region so boot keygen is unconditionally armed (generateCryptoKeyPair skips
|
|
// while region == UNSET unless the portduino sim-radio bypass applies), then reboot into the
|
|
// healthy keyed state every later test measures against.
|
|
void establishHealthyBaseline()
|
|
{
|
|
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_US;
|
|
TEST_ASSERT_TRUE(nodeDB->saveToDisk(SEGMENT_CONFIG));
|
|
rebootNodeDB();
|
|
// Reboot once more so any boot-time coercion of the freshly saved config (preset clamp)
|
|
// has reached its fixpoint on disk before we fingerprint it as the "good" file.
|
|
rebootNodeDB();
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_US, config.lora.region);
|
|
captureIdentityBaseline();
|
|
}
|
|
|
|
void assertIdentityMatchesBaseline()
|
|
{
|
|
TEST_ASSERT_EQUAL_UINT32(baseNodeNum, myNodeInfo.my_node_num);
|
|
TEST_ASSERT_EQUAL(32, config.security.public_key.size);
|
|
TEST_ASSERT_EQUAL_MEMORY(basePublicKey, config.security.public_key.bytes, 32);
|
|
TEST_ASSERT_EQUAL(32, config.security.private_key.size);
|
|
TEST_ASSERT_EQUAL_MEMORY(basePrivateKey, config.security.private_key.bytes, 32);
|
|
TEST_ASSERT_EQUAL(32, owner.public_key.size);
|
|
TEST_ASSERT_EQUAL_MEMORY(basePublicKey, owner.public_key.bytes, 32);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void setUp(void) {}
|
|
void tearDown(void) {}
|
|
|
|
// --- Healthy-boot identity ---
|
|
|
|
// The #11001 renumber family: a keyed boot must mint NodeNum == crc32(public_key) once, and
|
|
// every subsequent reboot must reproduce the same NodeNum, keypair and owner identity.
|
|
static void test_firstBoot_establishesKeyedIdentity(void)
|
|
{
|
|
TEST_MESSAGE("=== First keyed boot mints crc32(pubkey) identity ===");
|
|
establishHealthyBaseline();
|
|
|
|
TEST_ASSERT_EQUAL_UINT32(crc32Buffer(config.security.public_key.bytes, 32), myNodeInfo.my_node_num);
|
|
// The minted identity is in the store of record: self entry present, carrying our key.
|
|
const meshtastic_NodeInfoLite *self = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
TEST_ASSERT_NOT_NULL(self);
|
|
TEST_ASSERT_TRUE(nodeInfoLiteHasUser(self));
|
|
TEST_ASSERT_EQUAL(32, self->public_key.size);
|
|
TEST_ASSERT_EQUAL_MEMORY(basePublicKey, self->public_key.bytes, 32);
|
|
TEST_ASSERT_FALSE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
}
|
|
|
|
static void test_healthyReboot_preservesIdentity(void)
|
|
{
|
|
TEST_MESSAGE("=== Plain reboot: identity byte-identical, config.proto not rewritten ===");
|
|
const uint64_t fpBefore = fileFingerprint(configFileName);
|
|
TEST_ASSERT_NOT_EQUAL(0, fpBefore);
|
|
|
|
rebootNodeDB();
|
|
|
|
assertIdentityMatchesBaseline();
|
|
TEST_ASSERT_EQUAL_STRING(baseLongName, owner.long_name);
|
|
// A healthy boot has nothing to persist for config: the on-disk file is already the fixpoint.
|
|
TEST_ASSERT_EQUAL_UINT64(fpBefore, fileFingerprint(configFileName));
|
|
}
|
|
|
|
// --- Degraded boot: present-but-undecodable config ---
|
|
|
|
static void test_corruptConfig_freezesIdentity_leavesFileUntouched(void)
|
|
{
|
|
TEST_MESSAGE("=== Corrupt config.proto: frozen identity, radio silent, file untouched ===");
|
|
writeFileBytes(configFileName, kGarbage, sizeof(kGarbage));
|
|
const uint64_t fpGarbage = fileFingerprint(configFileName);
|
|
TEST_ASSERT_NOT_EQUAL(0, fpGarbage);
|
|
|
|
rebootNodeDB();
|
|
|
|
TEST_ASSERT_TRUE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
// Radio silent until the operator restores a config.
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_UNSET, config.lora.region);
|
|
TEST_ASSERT_FALSE(config.lora.tx_enabled);
|
|
// Keygen skipped: no replacement keypair minted into RAM...
|
|
TEST_ASSERT_EQUAL(0, config.security.private_key.size);
|
|
// ...and the identity carried by devicestate is untouched, so the NodeNum cannot move.
|
|
TEST_ASSERT_EQUAL_UINT32(baseNodeNum, myNodeInfo.my_node_num);
|
|
TEST_ASSERT_EQUAL(32, owner.public_key.size);
|
|
TEST_ASSERT_EQUAL_MEMORY(basePublicKey, owner.public_key.bytes, 32);
|
|
// The boot must not have overwritten the (maybe transiently) corrupt file with defaults.
|
|
TEST_ASSERT_EQUAL_UINT64(fpGarbage, fileFingerprint(configFileName));
|
|
}
|
|
|
|
// Runs against the still-degraded NodeDB from the previous test: runtime reconfiguration
|
|
// (admin set_config -> saveToDisk) must not be permanently blocked by the boot freeze.
|
|
static void test_degradedBoot_runtimeConfigSaveStillPersists(void)
|
|
{
|
|
TEST_MESSAGE("=== Degraded boot: an explicit runtime config save still lands ===");
|
|
TEST_ASSERT_TRUE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
const uint64_t fpGarbage = fileFingerprint(configFileName);
|
|
|
|
TEST_ASSERT_TRUE(nodeDB->saveToDisk(SEGMENT_CONFIG));
|
|
|
|
TEST_ASSERT_NOT_EQUAL(fpGarbage, fileFingerprint(configFileName));
|
|
// What landed is a decodable config again (the degraded-boot defaults).
|
|
static meshtastic_LocalConfig scratch;
|
|
TEST_ASSERT_EQUAL(LoadFileResult::LOAD_SUCCESS, nodeDB->loadProto(configFileName, meshtastic_LocalConfig_size,
|
|
sizeof(scratch), &meshtastic_LocalConfig_msg, &scratch));
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_UNSET, scratch.lora.region);
|
|
}
|
|
|
|
static void test_restoredConfig_recoversOriginalIdentity(void)
|
|
{
|
|
TEST_MESSAGE("=== Good config bytes restored: next boot is normal with the ORIGINAL identity ===");
|
|
writeFileBytes(configFileName, goodConfigBytes.data(), goodConfigBytes.size());
|
|
|
|
rebootNodeDB();
|
|
|
|
TEST_ASSERT_FALSE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_US, config.lora.region);
|
|
TEST_ASSERT_TRUE(config.lora.tx_enabled);
|
|
assertIdentityMatchesBaseline();
|
|
}
|
|
|
|
// --- Absent config: fresh install, not a freeze ---
|
|
|
|
static void test_absentConfig_takesFreshInstallPath(void)
|
|
{
|
|
TEST_MESSAGE("=== Absent config.proto: OTHER_FAILURE -> defaults + fresh keypair ===");
|
|
uint8_t previousPublicKey[32];
|
|
memcpy(previousPublicKey, basePublicKey, 32);
|
|
TEST_ASSERT_TRUE(FSCom.remove(configFileName));
|
|
|
|
rebootNodeDB();
|
|
|
|
// No usable contents to protect, so this is NOT the frozen path.
|
|
TEST_ASSERT_FALSE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_UNSET, config.lora.region);
|
|
|
|
// Re-arm keygen (region gate) and reboot into the replacement identity.
|
|
establishHealthyBaseline(); // re-captures the baseline for the remaining tests
|
|
|
|
// A fresh install mints a new keypair - and with it a new NodeNum, still crc32-derived.
|
|
// (This is the flip side of the DECODE_FAILED freeze: with the file genuinely gone there
|
|
// is no identity left to preserve.)
|
|
TEST_ASSERT_EQUAL(32, config.security.public_key.size);
|
|
TEST_ASSERT_TRUE(memcmp(previousPublicKey, config.security.public_key.bytes, 32) != 0);
|
|
TEST_ASSERT_EQUAL_UINT32(crc32Buffer(config.security.public_key.bytes, 32), myNodeInfo.my_node_num);
|
|
TEST_ASSERT_TRUE(FSCom.exists(configFileName));
|
|
}
|
|
|
|
// --- Freeze is config-scoped ---
|
|
|
|
static void test_corruptNodesDb_doesNotFreezeIdentity(void)
|
|
{
|
|
TEST_MESSAGE("=== Corrupt nodes.proto alone: config loads, keygen runs, NodeNum kept ===");
|
|
const uint64_t fpConfig = fileFingerprint(configFileName);
|
|
writeFileBytes(nodeDatabaseFileName, kGarbage, sizeof(kGarbage));
|
|
|
|
rebootNodeDB();
|
|
|
|
TEST_ASSERT_FALSE(NodeDBTestShim::decodeFailed(nodeDB));
|
|
TEST_ASSERT_EQUAL(meshtastic_Config_LoRaConfig_RegionCode_US, config.lora.region);
|
|
assertIdentityMatchesBaseline();
|
|
// The store rebuilt from defaults still contains us.
|
|
const meshtastic_NodeInfoLite *self = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
TEST_ASSERT_NOT_NULL(self);
|
|
TEST_ASSERT_TRUE(nodeInfoLiteHasUser(self));
|
|
TEST_ASSERT_EQUAL_UINT64(fpConfig, fileFingerprint(configFileName));
|
|
}
|
|
|
|
// --- Devicestate-loss owner recovery ---
|
|
|
|
// The recovery block in loadFromDisk() fires when device.proto decodes but is below
|
|
// DEVICESTATE_MIN_VER: identity fields survive (my_node_num is in the decoded struct), the
|
|
// defaults overwrite the owner names, and the own-node entry in nodes.proto restores them.
|
|
static void test_oldDevicestate_recoversOwnerFromNodeDb(void)
|
|
{
|
|
TEST_MESSAGE("=== Old-version devicestate: owner names recovered from own NodeDB entry ===");
|
|
// Put the recoverable names into the store of record...
|
|
strncpy(owner.long_name, "Recovered Owner", sizeof(owner.long_name));
|
|
strncpy(owner.short_name, "RCVR", sizeof(owner.short_name));
|
|
meshtastic_NodeInfoLite *self = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
TEST_ASSERT_NOT_NULL(self);
|
|
TypeConversions::CopyUserToNodeInfoLite(self, owner);
|
|
TEST_ASSERT_TRUE(nodeDB->saveToDisk(SEGMENT_NODEDATABASE));
|
|
|
|
// ...then persist a devicestate that is valid but too old, carrying DIFFERENT names, so a
|
|
// recovered name can only have come from the nodes.proto entry.
|
|
strncpy(owner.long_name, "Stale Devicestate", sizeof(owner.long_name));
|
|
strncpy(owner.short_name, "STAL", sizeof(owner.short_name));
|
|
devicestate.version = DEVICESTATE_MIN_VER - 1;
|
|
TEST_ASSERT_TRUE(nodeDB->saveToDisk(SEGMENT_DEVICESTATE));
|
|
|
|
rebootNodeDB();
|
|
|
|
TEST_ASSERT_EQUAL_STRING("Recovered Owner", owner.long_name);
|
|
TEST_ASSERT_EQUAL_STRING("RCVR", owner.short_name);
|
|
// Identity survives the devicestate discard: the NodeNum in the old file is carried over
|
|
// and keygen re-derives the same crc32(public_key) value.
|
|
assertIdentityMatchesBaseline();
|
|
|
|
// The recovery is re-persisted: the on-disk devicestate is current-version with the
|
|
// recovered names, not the stale ones.
|
|
static meshtastic_DeviceState saved;
|
|
TEST_ASSERT_EQUAL(LoadFileResult::LOAD_SUCCESS, nodeDB->loadProto(deviceStateFileName, meshtastic_DeviceState_size,
|
|
sizeof(saved), &meshtastic_DeviceState_msg, &saved));
|
|
TEST_ASSERT_EQUAL(DEVICESTATE_CUR_VER, saved.version);
|
|
TEST_ASSERT_EQUAL_STRING("Recovered Owner", saved.owner.long_name);
|
|
}
|
|
|
|
// --- loadProto classification ---
|
|
|
|
// The wipe cascade lived in the difference between these verdicts: DECODE_FAILED is the only
|
|
// protected path, and loadProto never returns NOT_FOUND (an unopenable file is OTHER_FAILURE).
|
|
static void test_loadProto_classifiesFailuresDistinctly(void)
|
|
{
|
|
TEST_MESSAGE("=== loadProto: absent=OTHER_FAILURE, garbage/truncated=DECODE_FAILED ===");
|
|
const char *scratchPath = "/prefs/nbr_scratch.proto";
|
|
static meshtastic_LocalConfig scratch;
|
|
|
|
FSCom.remove(scratchPath);
|
|
TEST_ASSERT_EQUAL(LoadFileResult::OTHER_FAILURE, nodeDB->loadProto(scratchPath, meshtastic_LocalConfig_size, sizeof(scratch),
|
|
&meshtastic_LocalConfig_msg, &scratch));
|
|
|
|
writeFileBytes(scratchPath, kGarbage, sizeof(kGarbage));
|
|
TEST_ASSERT_EQUAL(LoadFileResult::DECODE_FAILED, nodeDB->loadProto(scratchPath, meshtastic_LocalConfig_size, sizeof(scratch),
|
|
&meshtastic_LocalConfig_msg, &scratch));
|
|
|
|
// A torn write: a valid encoding minus its final byte always cuts the last field short.
|
|
TEST_ASSERT_GREATER_THAN(1, goodConfigBytes.size());
|
|
writeFileBytes(scratchPath, goodConfigBytes.data(), goodConfigBytes.size() - 1);
|
|
TEST_ASSERT_EQUAL(LoadFileResult::DECODE_FAILED, nodeDB->loadProto(scratchPath, meshtastic_LocalConfig_size, sizeof(scratch),
|
|
&meshtastic_LocalConfig_msg, &scratch));
|
|
|
|
// The unmodified bytes still decode - the failure above was the truncation, nothing else.
|
|
writeFileBytes(scratchPath, goodConfigBytes.data(), goodConfigBytes.size());
|
|
TEST_ASSERT_EQUAL(LoadFileResult::LOAD_SUCCESS, nodeDB->loadProto(scratchPath, meshtastic_LocalConfig_size, sizeof(scratch),
|
|
&meshtastic_LocalConfig_msg, &scratch));
|
|
|
|
FSCom.remove(scratchPath); // leave nothing behind
|
|
}
|
|
|
|
// installDefaultModuleConfig() sized its memset to meshtastic_ModuleConfig (the 368-byte oneof)
|
|
// instead of the 1092-byte LocalModuleConfig, so submessages it never assigns kept their old values.
|
|
static void test_oldModuleConfig_discardClearsTailSubmessages(void)
|
|
{
|
|
// statusmessage sits at offset 609, past the old 368-byte clear, and the defaults installer
|
|
// never assigns it - so only the full-struct memset can drop this marker.
|
|
moduleConfig = meshtastic_LocalModuleConfig_init_zero;
|
|
moduleConfig.version = DEVICESTATE_MIN_VER - 1;
|
|
moduleConfig.has_statusmessage = true;
|
|
strncpy(moduleConfig.statusmessage.node_status, "STALE-TAIL", sizeof(moduleConfig.statusmessage.node_status) - 1);
|
|
|
|
uint8_t buf[meshtastic_LocalModuleConfig_size];
|
|
size_t len = pb_encode_to_bytes(buf, sizeof(buf), &meshtastic_LocalModuleConfig_msg, &moduleConfig);
|
|
TEST_ASSERT_GREATER_THAN_size_t(0, len);
|
|
writeFileBytes(moduleConfigFileName, buf, len);
|
|
|
|
rebootNodeDB(); // decodes, sees version < DEVICESTATE_MIN_VER, discards
|
|
|
|
// The opt-in migration re-stamps the version after the discard, so assert the floor, not equality.
|
|
TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(DEVICESTATE_CUR_VER, moduleConfig.version, "discard did not reinstall defaults");
|
|
TEST_ASSERT_EQUAL_STRING_MESSAGE("", moduleConfig.statusmessage.node_status,
|
|
"statusmessage survived the moduleConfig discard");
|
|
|
|
FSCom.remove(moduleConfigFileName); // leave the sandbox as we found it
|
|
rebootNodeDB();
|
|
}
|
|
|
|
NBR_TEST_ENTRY void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
nodeDB = new NodeDB(); // first boot on the pristine per-suite sandbox
|
|
|
|
UNITY_BEGIN();
|
|
|
|
printf("\n=== Healthy-boot identity ===\n");
|
|
RUN_TEST(test_firstBoot_establishesKeyedIdentity);
|
|
RUN_TEST(test_healthyReboot_preservesIdentity);
|
|
|
|
printf("\n=== Degraded boot (corrupt config) ===\n");
|
|
RUN_TEST(test_corruptConfig_freezesIdentity_leavesFileUntouched);
|
|
RUN_TEST(test_degradedBoot_runtimeConfigSaveStillPersists);
|
|
RUN_TEST(test_restoredConfig_recoversOriginalIdentity);
|
|
|
|
printf("\n=== Fresh install vs freeze scoping ===\n");
|
|
RUN_TEST(test_absentConfig_takesFreshInstallPath);
|
|
RUN_TEST(test_corruptNodesDb_doesNotFreezeIdentity);
|
|
|
|
printf("\n=== Devicestate recovery + loadProto classification ===\n");
|
|
RUN_TEST(test_oldDevicestate_recoversOwnerFromNodeDb);
|
|
RUN_TEST(test_loadProto_classifiesFailuresDistinctly);
|
|
|
|
printf("\n=== ModuleConfig discard completeness ===\n");
|
|
RUN_TEST(test_oldModuleConfig_discardClearsTailSubmessages);
|
|
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
NBR_TEST_ENTRY void loop() {}
|
|
|
|
#else // !FSCom || PKI excluded
|
|
|
|
void setUp(void) {}
|
|
void tearDown(void) {}
|
|
|
|
NBR_TEST_ENTRY void setup()
|
|
{
|
|
initializeTestEnvironment();
|
|
UNITY_BEGIN();
|
|
exit(UNITY_END());
|
|
}
|
|
|
|
NBR_TEST_ENTRY void loop() {}
|
|
|
|
#endif
|