From 51eadb77d4c0daeb84aecc61f82ac4dd1450067e Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 14 Aug 2026 19:37:01 +0000 Subject: [PATCH] fix(NodeDB): reset a persisted event firmware_edition on vanilla builds (#11504) * fix(NodeDB): reset a persisted event firmware_edition on vanilla builds myNodeInfo lives in devicestate, which survives a firmware reinstall, and the boot-time edition stamp was compiled out entirely on builds without USERPREFS_FIRMWARE_EDITION. A device flashed from an event build back to vanilla therefore kept reporting the event edition forever, and clients kept its branding until a factory reset. Stamp VANILLA in the else branch so the running build is always the source of truth. * Stamp the edition before the boot save decision, and assert the on-disk value Review follow-up: the stamp sat after the devicestate CRC compare, so an edition-only change stayed RAM-only and the persisted event edition survived on disk. Move it next to the other running-build-wins fixups (device_id, min_app_version), which run inside the CRC window, and extend the test to read device.proto back so the persisted value is asserted too. --- src/mesh/NodeDB.cpp | 11 ++++-- test/state-manifest.tsv | 1 + test/test_firmware_edition/test_main.cpp | 49 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 test/test_firmware_edition/test_main.cpp diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6f87422f2..715daff1b 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -430,6 +430,14 @@ NodeDB::NodeDB() // likewise - we always want the app requirements to come from the running appload myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00 + + // likewise the edition: it lives in persisted devicestate, so a vanilla install must + // overwrite the previous event build's value. Before the CRC compare, so the change persists. +#ifdef USERPREFS_FIRMWARE_EDITION + myNodeInfo.firmware_edition = USERPREFS_FIRMWARE_EDITION; +#else + myNodeInfo.firmware_edition = meshtastic_FirmwareEdition_VANILLA; +#endif pickNewNodeNum(); // Set our board type so we can share it with others @@ -615,9 +623,6 @@ NodeDB::NodeDB() config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; config.position.gps_enabled = 0; } -#ifdef USERPREFS_FIRMWARE_EDITION - myNodeInfo.firmware_edition = USERPREFS_FIRMWARE_EDITION; -#endif #ifdef USERPREFS_FIXED_GPS if (myNodeInfo.reboot_count == 1) { // Check if First boot ever or after Factory Reset. meshtastic_Position fixedGPS = meshtastic_Position_init_default; diff --git a/test/state-manifest.tsv b/test/state-manifest.tsv index 45a8fa962..7420504e8 100644 --- a/test/state-manifest.tsv +++ b/test/state-manifest.tsv @@ -42,6 +42,7 @@ # suite flags reason test_admin_radio writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto,warm.dat,Messages_default.msgs per-test NodeDB fixture, and the admin handlers under test persist config, channels and node metadata test_admin_session_repro writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto constructs a NodeDB, whose constructor persists a default set when the prefs directory is empty +test_firmware_edition writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto persists an event firmware_edition in devicestate, then reboots a NodeDB to prove a vanilla build resets it test_fuzz_packets writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto,warm.dat,Messages_default.msgs drives decode of fuzzed packets through the real NodeDB and message store test_hop_scaling writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto constructs a NodeDB to hold the hop-distance fixtures test_mesh_beacon writes=module.proto exercises the beacon's module-config save path diff --git a/test/test_firmware_edition/test_main.cpp b/test/test_firmware_edition/test_main.cpp new file mode 100644 index 000000000..949dd0335 --- /dev/null +++ b/test/test_firmware_edition/test_main.cpp @@ -0,0 +1,49 @@ +// devicestate.my_node survives a firmware reinstall, so a vanilla build (no +// USERPREFS_FIRMWARE_EDITION) must reset a persisted event edition at boot. +#include "MeshTypes.h" // Include BEFORE TestUtil.h +#include "TestUtil.h" +#include "mesh/NodeDB.h" +#include + +#if defined(ARCH_PORTDUINO) +#define FE_TEST_ENTRY extern "C" +#else +#define FE_TEST_ENTRY +#endif + +void setUp(void) {} +void tearDown(void) {} + +static meshtastic_FirmwareEdition persistedEdition() +{ + meshtastic_DeviceState saved = meshtastic_DeviceState_init_zero; + TEST_ASSERT_EQUAL(LoadFileResult::LOAD_SUCCESS, nodeDB->loadProto(deviceStateFileName, meshtastic_DeviceState_size, + sizeof(saved), &meshtastic_DeviceState_msg, &saved)); + return saved.my_node.firmware_edition; +} + +static void test_vanillaBoot_resetsPersistedEventEdition(void) +{ + devicestate.my_node.firmware_edition = meshtastic_FirmwareEdition_DEFCON; + TEST_ASSERT_TRUE(nodeDB->saveToDisk(SEGMENT_DEVICESTATE)); + TEST_ASSERT_EQUAL(meshtastic_FirmwareEdition_DEFCON, persistedEdition()); + + NodeDB *rebooted = new NodeDB(); + delete nodeDB; + nodeDB = rebooted; + + TEST_ASSERT_EQUAL(meshtastic_FirmwareEdition_VANILLA, devicestate.my_node.firmware_edition); + // On disk too, not just in RAM: the stamp must land before the boot save decision. + TEST_ASSERT_EQUAL(meshtastic_FirmwareEdition_VANILLA, persistedEdition()); +} + +FE_TEST_ENTRY void setup() +{ + initializeTestEnvironment(); + nodeDB = new NodeDB(); + + UNITY_BEGIN(); + RUN_TEST(test_vanillaBoot_resetsPersistedEventEdition); + exit(UNITY_END()); +} +FE_TEST_ENTRY void loop() {}