mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
fix(companion): prefs schema corruption, broken multi-scope, stale relay filter, cursor row
Follow-up review of f589b9b2 -- five defects in that commit's own changes.
- repeat_scope_only + repeat_extra_scopes were read/written in the MIDDLE of
the prefs stream, beside their repeat_* siblings. loadPrefsInt()'s rd() is a
plain sequential reader gated only on file.available(), with no per-field
versioning, so on any pre-existing file those 25 bytes were taken from the
fields that follow, shifting EVERY later field: repeater profile (incl. a
float freq), track_shared_loc, all of loc_share_*, trail, bot, GPIO modes.
Moved to the struct/file tail, sentinel bumped to 0xC0DE0027 with 0xC0DE0026
marked burned. sizeof stays 2752 (confirmed by build); the tripwire procedure
now spells out the append-only rule that "in struct order" left implicit.
- rebuildRepeatScopes() called getAutoKeyFor() with id 0 for every entry, but
that cache is keyed on the id alone and ignores the name on a hit -- so every
extra scope after the first silently got the first one's key, making the
comma-separated list do nothing. Distinct id per scope now.
- interference_threshold had no load clamp, so an upgrader read 0x23 (35) out
of the old file's sentinel tail instead of 0.
- CMD_SET_DEFAULT_FLOOD_SCOPE wrote default_scope_key without rebuilding the
relay filter, so setting or clearing the scope from the app left the repeater
filtering on the previous key until reboot. The on-device path already did.
- The keyboard preview derived the cursor's row a second time from byte
offsets, disagreeing with the cursor_line the scroll window already computes:
it pinned the cursor to the end of a full line (drawing '_' one character
past the display width) at every wrap boundary. Use cursor_line directly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -383,18 +383,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
(_prefs.repeat_min_snr < -20 || _prefs.repeat_min_snr > 10))
|
||||
_prefs.repeat_min_snr = NodePrefs::REPEAT_SNR_DISABLED; // match the UI's -20..10 range
|
||||
if (_prefs.repeat_suppress_dup > 1) _prefs.repeat_suppress_dup = 0;
|
||||
|
||||
// → 0xC0DE0025: append repeat_scope_only right after the other repeater
|
||||
// forwarding filters. A pre-0x25 file has no byte here; clamp to 0 (off,
|
||||
// unchanged forwarding behaviour for upgraders).
|
||||
rd(&_prefs.repeat_scope_only, sizeof(_prefs.repeat_scope_only));
|
||||
if (_prefs.repeat_scope_only > 1) _prefs.repeat_scope_only = 0;
|
||||
|
||||
// → 0xC0DE0026: append repeat_extra_scopes right after it. A pre-0x26 file
|
||||
// has no bytes here; rd() zero-inits, which is already an empty string.
|
||||
rd(_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
||||
_prefs.repeat_extra_scopes[sizeof(_prefs.repeat_extra_scopes) - 1] = '\0';
|
||||
|
||||
// NOTE: repeat_scope_only/repeat_extra_scopes are read at the TAIL, not here
|
||||
// beside their siblings — see the append-only rule in NodePrefs.h.
|
||||
rd(&_prefs.repeater_use_profile, sizeof(_prefs.repeater_use_profile));
|
||||
rd(&_prefs.repeater_freq, sizeof(_prefs.repeater_freq));
|
||||
rd(&_prefs.repeater_bw, sizeof(_prefs.repeater_bw));
|
||||
@@ -582,12 +572,27 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
if (_prefs.keyboard_cardkb_compact > 1) _prefs.keyboard_cardkb_compact = 0;
|
||||
|
||||
// → 0xC0DE0024: append interference_threshold + cad_enabled at the tail.
|
||||
// A pre-0x24 file has no bytes here; clamp to 0 (both off, matching the
|
||||
// getters' previous hardcoded behaviour for upgraders).
|
||||
// A pre-0x24 file has no bytes here, so these read that file's own 4-byte
|
||||
// sentinel tail (0xC0DE0023 → 23 00 DE C0) rather than zeroes; clamp both to
|
||||
// 0/off, matching the getters' previous hardcoded behaviour for upgraders.
|
||||
// interference_threshold is dB above the measured noise floor (see
|
||||
// RadioLibWrapper::isChannelActive()); anything past ~30 dB would never
|
||||
// trigger anyway, so treat it as a stray byte and fall back to off.
|
||||
rd(&_prefs.interference_threshold, sizeof(_prefs.interference_threshold));
|
||||
if (_prefs.interference_threshold > 30) _prefs.interference_threshold = 0;
|
||||
rd(&_prefs.cad_enabled, sizeof(_prefs.cad_enabled));
|
||||
if (_prefs.cad_enabled > 1) _prefs.cad_enabled = 0;
|
||||
|
||||
// → 0xC0DE0027: append repeat_scope_only + repeat_extra_scopes at the tail.
|
||||
// (0xC0DE0026 had them mid-stream, which shifted every later field by 25
|
||||
// bytes when reading an older file — see NodePrefs.h.) A pre-0x27 file has
|
||||
// no bytes here; clamp the flag to 0 (off, unchanged forwarding behaviour)
|
||||
// and leave the name list zero-initialised, which is already an empty string.
|
||||
rd(&_prefs.repeat_scope_only, sizeof(_prefs.repeat_scope_only));
|
||||
if (_prefs.repeat_scope_only > 1) _prefs.repeat_scope_only = 0;
|
||||
rd(_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
||||
_prefs.repeat_extra_scopes[sizeof(_prefs.repeat_extra_scopes) - 1] = '\0';
|
||||
|
||||
// Schema sentinel: bumped on layout changes. Mismatch means an older file
|
||||
// (or a different schema); rd() already zero-inits any fields not present,
|
||||
// so we just log it — next savePrefs writes the current sentinel.
|
||||
@@ -748,8 +753,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.repeat_delay_boost, sizeof(_prefs.repeat_delay_boost));
|
||||
file.write((uint8_t *)&_prefs.repeat_min_snr, sizeof(_prefs.repeat_min_snr));
|
||||
file.write((uint8_t *)&_prefs.repeat_suppress_dup, sizeof(_prefs.repeat_suppress_dup));
|
||||
file.write((uint8_t *)&_prefs.repeat_scope_only, sizeof(_prefs.repeat_scope_only));
|
||||
file.write((uint8_t *)_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
||||
// repeat_scope_only/repeat_extra_scopes are written at the TAIL — see loadPrefsInt().
|
||||
file.write((uint8_t *)&_prefs.repeater_use_profile, sizeof(_prefs.repeater_use_profile));
|
||||
file.write((uint8_t *)&_prefs.repeater_freq, sizeof(_prefs.repeater_freq));
|
||||
file.write((uint8_t *)&_prefs.repeater_bw, sizeof(_prefs.repeater_bw));
|
||||
@@ -804,6 +808,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.keyboard_cardkb_compact, sizeof(_prefs.keyboard_cardkb_compact));
|
||||
file.write((uint8_t *)&_prefs.interference_threshold, sizeof(_prefs.interference_threshold));
|
||||
file.write((uint8_t *)&_prefs.cad_enabled, sizeof(_prefs.cad_enabled));
|
||||
file.write((uint8_t *)&_prefs.repeat_scope_only, sizeof(_prefs.repeat_scope_only));
|
||||
file.write((uint8_t *)_prefs.repeat_extra_scopes, sizeof(_prefs.repeat_extra_scopes));
|
||||
|
||||
// Tail sentinel — must be last. See NodePrefs::SCHEMA_SENTINEL. Its write is
|
||||
// the one we check: once the flash fills, writes return 0, so a good
|
||||
|
||||
Reference in New Issue
Block a user