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:
@@ -208,24 +208,15 @@ struct NodePrefs { // persisted to file
|
||||
// fringe traffic isn't re-flooded. REPEAT_SNR_DISABLED (-128) = off.
|
||||
// repeat_suppress_dup: 1 = cancel a queued retransmit when the same flood is
|
||||
// overheard from another node first (less redundant airtime in dense mesh).
|
||||
// repeat_scope_only: 1 = only forward flood packets matching this device's
|
||||
// own scope (Settings > Radio > Scope, default_scope_key) or one of the
|
||||
// repeat_extra_scopes below — drops unscoped floods and floods tagged for
|
||||
// a different community. A no-op (forwards everything, unchanged) while
|
||||
// no scope is configured at all, so enabling this on an unconfigured
|
||||
// device can't silently blackhole all flood traffic.
|
||||
// (repeat_scope_only / repeat_extra_scopes belong to this group too, but live
|
||||
// at the struct tail — the file format is strictly append-only, so a new
|
||||
// field can never be slotted in next to its logical siblings.)
|
||||
uint8_t repeat_skip_adverts;
|
||||
uint8_t repeat_max_hops;
|
||||
uint8_t repeat_delay_boost;
|
||||
int8_t repeat_min_snr;
|
||||
static const int8_t REPEAT_SNR_DISABLED = -128;
|
||||
uint8_t repeat_suppress_dup;
|
||||
uint8_t repeat_scope_only;
|
||||
// Extra region names this repeater also relays for, beyond its own
|
||||
// Settings > Radio > Scope (comma-separated, e.g. "eu,de") — see
|
||||
// MyMesh::rebuildRepeatScopes(). Relay-only: never affects what scope the
|
||||
// companion's own messages send under, only what repeat_scope_only accepts.
|
||||
char repeat_extra_scopes[24];
|
||||
|
||||
// Optional dedicated radio profile for repeater mode. When repeater_use_profile
|
||||
// is 1, enabling the repeater switches the radio to repeater_freq/bw/sf/cr and
|
||||
@@ -463,6 +454,22 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t interference_threshold;
|
||||
uint8_t cad_enabled;
|
||||
|
||||
// Repeater scope filter — logically part of the repeat_* group far above, but
|
||||
// the on-disk format is append-only (see the tripwire note at the bottom of
|
||||
// this file), so it has to live here at the tail.
|
||||
// repeat_scope_only: 1 = only forward flood packets matching this device's
|
||||
// own scope (Settings > Radio > Scope, default_scope_key) or one of the
|
||||
// repeat_extra_scopes below — drops unscoped floods and floods tagged for
|
||||
// a different community. A no-op (forwards everything, unchanged) while
|
||||
// no scope is configured at all, so enabling this on an unconfigured
|
||||
// device can't silently blackhole all flood traffic.
|
||||
uint8_t repeat_scope_only;
|
||||
// Extra region names this repeater also relays for, beyond its own
|
||||
// Settings > Radio > Scope (comma-separated, e.g. "eu,de") — see
|
||||
// MyMesh::rebuildRepeatScopes(). Relay-only: never affects what scope the
|
||||
// companion's own messages send under, only what repeat_scope_only accepts.
|
||||
char repeat_extra_scopes[24];
|
||||
|
||||
// Single source of truth for the live-share option tables (shared by the Map
|
||||
// UI labels and the auto-send engine in UITask).
|
||||
static const uint8_t LOC_SHARE_MOVE_COUNT = 4;
|
||||
@@ -525,7 +532,12 @@ struct NodePrefs { // persisted to file
|
||||
// adding/removing/reordering fields in DataStore::savePrefs/loadPrefsInt so
|
||||
// older saves are detected on load and skipped (zero-init defaults kept).
|
||||
// High 24 bits identify the file format; low byte is the schema revision.
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0026;
|
||||
// 0xC0DE0026 is BURNED — it briefly named a layout that put repeat_scope_only
|
||||
// + repeat_extra_scopes in the middle of the stream (next to the other
|
||||
// repeat_* fields) instead of at the tail, which shifted every field after
|
||||
// them by 25 bytes when loading an older file. Never released, but a dev
|
||||
// build wrote it, so the number must not be reused for anything else.
|
||||
static const uint32_t SCHEMA_SENTINEL = 0xC0DE0027;
|
||||
|
||||
// Bit-index for each home page. Used by page_order (entries store bit+1) and
|
||||
// by home_pages_mask. Single source of truth — both HomeScreen::pageBit/bitToPage
|
||||
@@ -617,9 +629,18 @@ struct NodePrefs { // persisted to file
|
||||
//
|
||||
// This assert is the manual checkpoint. Changing a data member changes sizeof
|
||||
// and trips it. When it trips, do ALL of the following, then update the number:
|
||||
// 0. put the new field at the TAIL of the struct, even when it logically
|
||||
// belongs beside older siblings. loadPrefsInt()'s rd() is a plain
|
||||
// sequential reader gated only on file.available() — there is no per-field
|
||||
// versioning — so a field inserted mid-stream is read out of an older
|
||||
// file's bytes and shifts EVERY field after it (repeat_scope_only +
|
||||
// repeat_extra_scopes did exactly that in the burned 0xC0DE0026 layout).
|
||||
// "In struct order" below therefore means "appended in both places".
|
||||
// 1. add the field's rd(...) in DataStore::loadPrefsInt(), in struct order
|
||||
// 2. add the field's write(...) in DataStore::savePrefs(), in struct order
|
||||
// 3. clamp it on load (an upgrader's file lacks it → stray bytes)
|
||||
// 3. clamp it on load (an upgrader's file lacks it → stray bytes; the first
|
||||
// few are that file's own 4-byte sentinel tail, so a plausible-looking
|
||||
// value like 0x23 shows up rather than 0)
|
||||
// 4. bump SCHEMA_SENTINEL's low byte
|
||||
// (Padding can also shift sizeof; a "false" trip just means re-check + rebump.)
|
||||
// keyboard_cardkb_compact (0xC0DE0023) also landed in existing tail padding --
|
||||
@@ -637,6 +658,10 @@ struct NodePrefs { // persisted to file
|
||||
// 0xC0DE0024 bump -- confirmed via a real build, sizeof unchanged at 2728.
|
||||
// repeat_extra_scopes[24] (0xC0DE0026) added exactly 24 bytes, no leftover
|
||||
// padding this time -- confirmed via a real build, sizeof now 2752.
|
||||
// 0xC0DE0027 moved those same two fields out of the repeat_* group and down to
|
||||
// the struct tail (the 0xC0DE0026 mid-stream layout was unreadable for older
|
||||
// files, see the sentinel comment) -- padding worked out identically either
|
||||
// way, so sizeof stays 2752. Confirmed via a real Heltec_v3 build.
|
||||
static_assert(sizeof(NodePrefs) == 2752,
|
||||
"NodePrefs layout changed — sync DataStore save/load + clamp, bump "
|
||||
"SCHEMA_SENTINEL, then update this size (see steps above).");
|
||||
|
||||
Reference in New Issue
Block a user