mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-10-07 02:06:40 +00:00
fix(companion): restore the v1.27 -> v1.28 scope migrations
eed6d31d dropped two one-time migrations as "old cruft", but both are needed
by exactly the upgrade path this release ships: v1.27 has sentinel 0xC0DE002A
and the scope list is new in this cycle.
- loadScopeList() again turns an existing single default_scope_name/key into
list entry 1 (default) and MyMesh::begin() seeds the channels that already
exist with it. Without it an upgrader's DMs and channels silently go out
unscoped, contradicting the release note.
- loadPrefsInt() again zeroes repeat_extra_scope_mask/ch_scope_idx when the
file predates 0xC0DE002B, so the old sentinel tail can't read back as real
scope picks.
The older-than-v1.27 backfills stay removed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -622,6 +622,19 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
|||||||
if (sentinel != NodePrefs::SCHEMA_SENTINEL) {
|
if (sentinel != NodePrefs::SCHEMA_SENTINEL) {
|
||||||
MESH_DEBUG_PRINTLN("prefs schema sentinel mismatch: got 0x%08X, expected 0x%08X — re-saving on next change",
|
MESH_DEBUG_PRINTLN("prefs schema sentinel mismatch: got 0x%08X, expected 0x%08X — re-saving on next change",
|
||||||
(unsigned)sentinel, (unsigned)NodePrefs::SCHEMA_SENTINEL);
|
(unsigned)sentinel, (unsigned)NodePrefs::SCHEMA_SENTINEL);
|
||||||
|
// 0xC0DE002A (v1.27) → 0xC0DE002B: repeat_extra_scope_mask + ch_scope_idx
|
||||||
|
// appended. Unlike the range-clamped fields, these can't be left with whatever
|
||||||
|
// stray bytes rd() picked up from a pre-0x2B file's own sentinel tail:
|
||||||
|
// every bit/byte value is "valid" (any mask or index could be a real pick),
|
||||||
|
// so garbage here isn't caught by a range clamp -- it just silently
|
||||||
|
// masquerades as a real one, and can even reactivate later once the scope
|
||||||
|
// list grows long enough to reach an index that used to be out of range.
|
||||||
|
// Zero both outright on this transition; a fresh scope list is empty
|
||||||
|
// anyway, so there's nothing genuine to lose.
|
||||||
|
if (sentinel < 0xC0DE002B) {
|
||||||
|
_prefs.repeat_extra_scope_mask = 0;
|
||||||
|
memset(_prefs.ch_scope_idx, 0, sizeof(_prefs.ch_scope_idx));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
@@ -1037,33 +1050,48 @@ void DataStore::saveChannels(DataStoreHost* host) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataStore::loadScopeList(ScopeList& list) {
|
bool DataStore::loadScopeList(ScopeList& list, const NodePrefs& prefs) {
|
||||||
File file = openRead("/scopes1");
|
File file = openRead("/scopes1");
|
||||||
if (!file) {
|
if (file) {
|
||||||
// No /scopes1 -- fresh device, stays at the default-constructed ScopeList
|
uint8_t hdr[2] = { 0, 0 }; // default_idx is read back below even if the header read fails
|
||||||
// (empty, default_idx 0 == "*").
|
bool success = (file.read(hdr, 2) == 2);
|
||||||
list.count = 0;
|
uint8_t count = success ? hdr[1] : 0;
|
||||||
list.default_idx = 0;
|
if (count > ScopeList::MAX_SCOPE_ENTRIES) count = 0; // corrupt header -- start empty rather than overrun entries[]
|
||||||
return;
|
|
||||||
|
uint8_t loaded = 0;
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
ScopeEntry e;
|
||||||
|
bool ok = (file.read((uint8_t *)e.name, sizeof(e.name)) == sizeof(e.name));
|
||||||
|
ok = ok && (file.read(e.key, sizeof(e.key)) == sizeof(e.key));
|
||||||
|
if (!ok) break; // truncated file -- keep whatever loaded fine so far
|
||||||
|
e.name[sizeof(e.name) - 1] = '\0';
|
||||||
|
list.entries[loaded++] = e;
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
list.count = loaded;
|
||||||
|
list.default_idx = list.clamp(hdr[0]);
|
||||||
|
return false; // the file was already there -- nothing migrated this boot
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t hdr[2] = { 0, 0 }; // default_idx is read back below even if the header read fails
|
// No /scopes1 yet -- one-time migration of an existing single
|
||||||
bool success = (file.read(hdr, 2) == 2);
|
// default_scope_name/key (Settings > Radio > Scope, pre-list) into list
|
||||||
uint8_t count = success ? hdr[1] : 0;
|
// entry 1 and mark it default, which covers DMs and the relay filter. The
|
||||||
if (count > ScopeList::MAX_SCOPE_ENTRIES) count = 0; // corrupt header -- start empty rather than overrun entries[]
|
// caller finishes the job for channels by seeding their per-channel picks
|
||||||
|
// once channels[] is loaded (see this function's return value). A
|
||||||
uint8_t loaded = 0;
|
// never-configured device just stays at the default-constructed ScopeList
|
||||||
for (uint8_t i = 0; i < count; i++) {
|
// (empty, default_idx 0 == "*").
|
||||||
ScopeEntry e;
|
list.count = 0;
|
||||||
bool ok = (file.read((uint8_t *)e.name, sizeof(e.name)) == sizeof(e.name));
|
list.default_idx = 0;
|
||||||
ok = ok && (file.read(e.key, sizeof(e.key)) == sizeof(e.key));
|
bool migrated = (prefs.default_scope_name[0] != '\0');
|
||||||
if (!ok) break; // truncated file -- keep whatever loaded fine so far
|
if (migrated) {
|
||||||
e.name[sizeof(e.name) - 1] = '\0';
|
ScopeEntry& e = list.entries[0];
|
||||||
list.entries[loaded++] = e;
|
StrHelper::strncpy(e.name, prefs.default_scope_name, sizeof(e.name));
|
||||||
|
memcpy(e.key, prefs.default_scope_key, sizeof(e.key)); // already-derived key, no need to re-derive
|
||||||
|
list.count = 1;
|
||||||
|
list.default_idx = 1;
|
||||||
}
|
}
|
||||||
file.close();
|
saveScopeList(list); // write /scopes1 so this migration runs only once
|
||||||
list.count = loaded;
|
return migrated; // caller seeds the existing channels with entry 1
|
||||||
list.default_idx = list.clamp(hdr[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataStore::saveScopeList(const ScopeList& list) {
|
void DataStore::saveScopeList(const ScopeList& list) {
|
||||||
|
|||||||
@@ -46,10 +46,20 @@ public:
|
|||||||
// deleted slot is simply absent from the file, not written as empty).
|
// deleted slot is simply absent from the file, not written as empty).
|
||||||
bool loadChannels(DataStoreHost* host);
|
bool loadChannels(DataStoreHost* host);
|
||||||
void saveChannels(DataStoreHost* host);
|
void saveChannels(DataStoreHost* host);
|
||||||
// /scopes1: the shared named-scope list (see ScopeList.h). A device with no
|
// /scopes1: the shared named-scope list (see ScopeList.h). `prefs` is only
|
||||||
// file yet just starts with the default-constructed ScopeList (empty,
|
// read, for a one-time migration of a pre-existing single
|
||||||
// default_idx 0 == "*").
|
// default_scope_name/key into list entry 1 -- the file is authoritative
|
||||||
void loadScopeList(ScopeList& list);
|
// once it exists.
|
||||||
|
//
|
||||||
|
// Returns true ONLY when that legacy migration just ran, i.e. this boot is
|
||||||
|
// the first on a device that had a Scope configured the old way. The caller
|
||||||
|
// uses that to seed the channels that already exist with the migrated entry
|
||||||
|
// (see MyMesh::begin) -- channels carry their own scope now, so without the
|
||||||
|
// seed an upgrader's channel traffic would silently drop to unscoped even
|
||||||
|
// though their DMs kept the old scope. Returns false when /scopes1 was
|
||||||
|
// already there, and on a genuinely fresh device with nothing to migrate
|
||||||
|
// (list left empty, default_idx 0 == "*").
|
||||||
|
bool loadScopeList(ScopeList& list, const NodePrefs& prefs);
|
||||||
void saveScopeList(const ScopeList& list);
|
void saveScopeList(const ScopeList& list);
|
||||||
void migrateToSecondaryFS();
|
void migrateToSecondaryFS();
|
||||||
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
|
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]);
|
||||||
|
|||||||
@@ -1910,7 +1910,9 @@ void MyMesh::begin(bool has_display) {
|
|||||||
|
|
||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon);
|
_store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon);
|
||||||
_store->loadScopeList(_scope_list);
|
// True only on the first boot after upgrading a device that had the old
|
||||||
|
// single Scope field set -- acted on once the channels are loaded, below.
|
||||||
|
bool scope_migrated_legacy = _store->loadScopeList(_scope_list, _prefs);
|
||||||
rebuildRepeatScopes();
|
rebuildRepeatScopes();
|
||||||
|
|
||||||
// sanitise bad pref values. NaN/inf must be reset BEFORE constrain(): constrain
|
// sanitise bad pref values. NaN/inf must be reset BEFORE constrain(): constrain
|
||||||
@@ -1962,6 +1964,20 @@ void MyMesh::begin(bool has_display) {
|
|||||||
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First boot after upgrading from the single device-wide Scope field: every
|
||||||
|
// channel now carries its own pick, and an unset pick means "*" == unscoped,
|
||||||
|
// not "inherit the default". Left alone, an upgrader's channel traffic would
|
||||||
|
// quietly go out unscoped while their DMs kept the old scope. Seed only the
|
||||||
|
// slots that actually hold a channel today -- a blanket fill would also hand
|
||||||
|
// the scope to whatever channel gets created in an empty slot later on.
|
||||||
|
if (scope_migrated_legacy && _scope_list.default_idx >= 1) {
|
||||||
|
for (uint8_t i = 0; i < NodePrefs::MAX_SCOPED_CHANNELS; i++) {
|
||||||
|
ChannelDetails ch;
|
||||||
|
if (getChannel(i, ch) && ch.name[0]) _prefs.ch_scope_idx[i] = _scope_list.default_idx;
|
||||||
|
}
|
||||||
|
savePrefs();
|
||||||
|
}
|
||||||
|
|
||||||
applyRepeaterRadio(); // companion params, or the repeater profile if relaying with one set
|
applyRepeaterRadio(); // companion params, or the repeater profile if relaying with one set
|
||||||
applyApc(); // sets TX power to the ceiling and arms APC if enabled
|
applyApc(); // sets TX power to the ceiling and arms APC if enabled
|
||||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||||
|
|||||||
Reference in New Issue
Block a user