mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-09-26 00:15:07 +00:00
#415: a Lua app was covered by the app drawer whenever a message arrived. Reported by jadestarwatcher, reproduced by mysterywavi, both on T-Deck. Mine, from the #393 badge fix. Apps launched from the drawer deliberately leave it alive underneath (appTileCb says so outright), and my badge refresh rebuilt it on every unread-count change, with openAppDrawer() ending in a move_foreground that threw it on top of the running app. The refresh now runs only when nothing is drawn in front of the drawer, tested by sibling order rather than by listing the tools, so a tool added later is covered without anyone remembering to update a list. The status bar is excluded, since it legitimately floats above the drawer at all times and treating it as covering would have stopped the badges refreshing at all. Leaving the signature stale is what makes it self-healing: the check runs again each tick, so the count is right by the time the drawer is back in front, with no need to hook every close path. Scroll position is now preserved across the rebuild too, which my change had also been resetting. #410: on the M9 the accent variants appeared but could not be selected. Also mine, from the #387 work. The picker was handled below m9HandleArrowKey, which takes LEFT and RIGHT for the caret and returns, so the arrows never reached it: the box was drawn and nothing could touch it. Lifted above that call, as its own function rather than a second copy of the logic. #399, requested by @Danie10: an option to advertise a position near you rather than your address. Settings, GPS, cycling exact / 100 m / 250 m / 1 km. The displacement is derived from the node identity, so it is the same offset every time. That is the whole point rather than an implementation detail: a fresh random offset per advert would scatter points around the true position, and averaging a night of them would recover the centre exactly. A fixed displacement instead looks like a node that sits somewhere else, which is what a manually set location already looks like. It applies only to our own adverts; the map and the GPS page keep the real fix, because the aim is to tell other people less, not to lie to yourself. Schema v57. The host test caught the new field the moment it was added, which is what it is for. All nine S3 envs and both ESP32-P4 targets green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
208 lines
8.3 KiB
C++
208 lines
8.3 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "helpers/esp32/TouchPrefsSchema.h"
|
|
|
|
using TouchPrefsSchema::Config;
|
|
|
|
static Config safeDefaults() {
|
|
Config c = {};
|
|
c.magic = TouchPrefsSchema::MAGIC;
|
|
c.ver = TouchPrefsSchema::CURRENT_VERSION;
|
|
c.bright = 100;
|
|
c.rx_queue = 1;
|
|
c.web_mirror = 0;
|
|
c.remote_mode = 0;
|
|
c.remote_landscape = 1;
|
|
c.web_terminal = 0;
|
|
c.map_tile_debug = 0;
|
|
c.hist_sync_after = 2;
|
|
c.hist_per_chat = 250;
|
|
c.p4_antenna = 0;
|
|
c.retry_echo = 1;
|
|
c.console_monitor = 1;
|
|
c.boot_wifi_time = 0;
|
|
c.boot_wifi_open = 0;
|
|
c.theme_mode = 0;
|
|
c.gps_fuzz_m = 0;
|
|
return c;
|
|
}
|
|
|
|
int main() {
|
|
constexpr size_t v43_size = offsetof(Config, retry_echo);
|
|
constexpr size_t suffix_offset = offsetof(Config, web_mirror);
|
|
constexpr size_t v43_suffix_size = v43_size - suffix_offset;
|
|
static_assert(offsetof(Config, retry_echo) ==
|
|
offsetof(Config, p4_antenna) + sizeof(Config::p4_antenna),
|
|
"v45 retry field moved");
|
|
|
|
// The tail is append-only, so a blob written by an older firmware is always a
|
|
// strict PREFIX of the current layout. Asserting an exact size delta pins the
|
|
// test to one release and goes stale the moment the next field lands (it did:
|
|
// this used to demand "v45 appends exactly one byte"). Assert the invariant
|
|
// that actually matters instead — every historical blob is shorter, and the
|
|
// fields it never stored keep their defaults.
|
|
static_assert(v43_size < sizeof(Config), "v43 blobs must be a strict prefix of the current layout");
|
|
|
|
// A v43 blob has the current layout truncated before the newly appended
|
|
// retry byte. Every established field must survive byte-for-byte, and every
|
|
// field appended since must fail safe to its default.
|
|
Config v43 = safeDefaults();
|
|
v43.ver = 43;
|
|
v43.bright = 67;
|
|
v43.rx_queue = 0;
|
|
v43.web_mirror = 1;
|
|
v43.remote_mode = 1;
|
|
v43.remote_landscape = 0;
|
|
v43.web_terminal = 1;
|
|
v43.map_tile_debug = 1;
|
|
v43.hist_sync_after = 7;
|
|
v43.hist_per_chat = 0x1234;
|
|
v43.p4_antenna = 0x56;
|
|
v43.retry_echo = 0; // outside the stored v43 extent
|
|
v43.boot_wifi_time = 1; // ditto — must NOT reach the migrated config
|
|
v43.boot_wifi_open = 1;
|
|
|
|
Config migrated = safeDefaults();
|
|
uint8_t stored_version = 0;
|
|
assert(TouchPrefsSchema::overlayStored(migrated, &v43, v43_size, &stored_version));
|
|
assert(stored_version == 43);
|
|
assert(migrated.bright == 67);
|
|
assert(migrated.rx_queue == 0);
|
|
assert(migrated.web_mirror == 1);
|
|
assert(migrated.remote_mode == 1);
|
|
assert(migrated.remote_landscape == 0);
|
|
assert(migrated.web_terminal == 1);
|
|
assert(migrated.map_tile_debug == 1);
|
|
assert(migrated.hist_sync_after == 7);
|
|
assert(migrated.hist_per_chat == 0x1234);
|
|
assert(migrated.p4_antenna == 0x56);
|
|
assert(migrated.retry_echo == 1);
|
|
assert(migrated.boot_wifi_time == 0);
|
|
assert(migrated.boot_wifi_open == 0);
|
|
assert(migrated.loud_alerts == 0);
|
|
assert(migrated.theme_mode == 0);
|
|
assert(migrated.gps_fuzz_m == 0);
|
|
|
|
// Reproduce beta 57 exactly: retry was inserted before the old suffix, then
|
|
// the v43 bytes were copied and the full shifted v44 structure was written.
|
|
uint8_t broken_v44[sizeof(Config)] = {};
|
|
memcpy(broken_v44, &v43, suffix_offset);
|
|
broken_v44[offsetof(Config, ver)] = TouchPrefsSchema::BROKEN_MID_INSERT_VERSION;
|
|
broken_v44[suffix_offset] = 1; // inserted retry_echo, overwriting old web_mirror
|
|
memcpy(broken_v44 + suffix_offset + 1,
|
|
reinterpret_cast<const uint8_t*>(&v43) + suffix_offset,
|
|
v43_suffix_size);
|
|
|
|
const Config defaults = safeDefaults();
|
|
migrated = defaults;
|
|
stored_version = 0;
|
|
assert(TouchPrefsSchema::overlayStored(migrated, broken_v44,
|
|
sizeof(broken_v44), &stored_version));
|
|
assert(stored_version == 44);
|
|
assert(migrated.bright == 67); // unambiguous prefix is preserved
|
|
assert(migrated.rx_queue == 0);
|
|
assert(memcmp(reinterpret_cast<const uint8_t*>(&migrated) + suffix_offset,
|
|
reinterpret_cast<const uint8_t*>(&defaults) + suffix_offset,
|
|
sizeof(Config) - suffix_offset) == 0); // ambiguous suffix fails safe
|
|
|
|
// #383 appended boot_wifi_time + boot_wifi_open at the tail. A v53 blob is the
|
|
// current layout minus exactly those two bytes: everything before them must
|
|
// survive, and both opt-ins must come back OFF rather than inheriting whatever
|
|
// byte followed the blob — an inherited 1 would spend boot time on a Wi-Fi
|
|
// session the user never asked for.
|
|
constexpr size_t v53_size = offsetof(Config, boot_wifi_time);
|
|
static_assert(v53_size + sizeof(Config::boot_wifi_time) + sizeof(Config::boot_wifi_open)
|
|
+ sizeof(Config::loud_alerts) + sizeof(Config::theme_mode)
|
|
+ sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
|
"v53 is the current layout minus every byte appended since");
|
|
|
|
Config v53 = safeDefaults();
|
|
v53.ver = 53;
|
|
v53.bright = 42;
|
|
v53.retry_echo = 0;
|
|
v53.console_mode = 1;
|
|
v53.console_monitor = 0;
|
|
v53.kb_force_legacy = 1;
|
|
v53.boot_wifi_time = 1; // outside the stored v53 extent
|
|
v53.boot_wifi_open = 1;
|
|
|
|
migrated = safeDefaults();
|
|
migrated.boot_wifi_time = 0;
|
|
migrated.boot_wifi_open = 0;
|
|
stored_version = 0;
|
|
assert(TouchPrefsSchema::overlayStored(migrated, &v53, v53_size, &stored_version));
|
|
assert(stored_version == 53);
|
|
assert(migrated.bright == 42);
|
|
assert(migrated.retry_echo == 0);
|
|
assert(migrated.console_mode == 1);
|
|
assert(migrated.console_monitor == 0);
|
|
assert(migrated.kb_force_legacy == 1);
|
|
assert(migrated.boot_wifi_time == 0);
|
|
assert(migrated.boot_wifi_open == 0);
|
|
|
|
// Once rewritten at the current version the whole structure is authoritative,
|
|
// including an explicit retry opt-out, a deliberately enabled Remote Mode, and
|
|
// both #383 opt-ins turned on.
|
|
Config current = safeDefaults();
|
|
current.remote_mode = 1;
|
|
current.remote_landscape = 0;
|
|
current.retry_echo = 0;
|
|
current.boot_wifi_time = 1;
|
|
current.boot_wifi_open = 1;
|
|
current.loud_alerts = 1;
|
|
current.theme_mode = 1;
|
|
current.gps_fuzz_m = 150;
|
|
migrated = safeDefaults();
|
|
assert(TouchPrefsSchema::overlayStored(migrated, ¤t, sizeof(current), &stored_version));
|
|
assert(stored_version == TouchPrefsSchema::CURRENT_VERSION);
|
|
assert(memcmp(&migrated, ¤t, sizeof(current)) == 0);
|
|
|
|
// Once rewritten as v45, every field through retry_echo is authoritative;
|
|
// fields appended by later versions retain their safe defaults.
|
|
constexpr size_t v45_size = offsetof(Config, app_hide);
|
|
Config v45 = safeDefaults();
|
|
v45.ver = 45;
|
|
v45.remote_mode = 1;
|
|
v45.remote_landscape = 0;
|
|
v45.retry_echo = 0;
|
|
migrated = safeDefaults();
|
|
assert(TouchPrefsSchema::overlayStored(migrated, &v45, v45_size, &stored_version));
|
|
assert(stored_version == 45);
|
|
assert(migrated.remote_mode == 1);
|
|
assert(migrated.remote_landscape == 0);
|
|
assert(migrated.retry_echo == 0);
|
|
assert(migrated.app_hide == safeDefaults().app_hide);
|
|
assert(migrated.theme_mode == 0);
|
|
|
|
// v56 appends only theme_mode. It was written as v54 on the contributing
|
|
// branch, but v54 and v55 were taken by boot_wifi_* and loud_alerts before it
|
|
// merged, so the field sits behind those and the blob one version short of
|
|
// current is the one that must leave it at the Night default.
|
|
constexpr size_t v55_size = offsetof(Config, theme_mode);
|
|
static_assert(offsetof(Config, gps_fuzz_m) + sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
|
"gps_fuzz_m must remain last");
|
|
static_assert(v55_size + sizeof(Config::theme_mode) + sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
|
"v55 is the current layout minus theme_mode and gps_fuzz_m");
|
|
Config v55 = safeDefaults();
|
|
v55.ver = 55;
|
|
v55.kb_force_legacy = 1;
|
|
v55.loud_alerts = 1;
|
|
v55.theme_mode = 1; // outside the stored v55 extent
|
|
migrated = safeDefaults();
|
|
assert(TouchPrefsSchema::overlayStored(migrated, &v55, v55_size, &stored_version));
|
|
assert(stored_version == 55);
|
|
assert(migrated.kb_force_legacy == 1);
|
|
assert(migrated.loud_alerts == 1);
|
|
assert(migrated.theme_mode == 0);
|
|
assert(migrated.gps_fuzz_m == 0);
|
|
|
|
Config invalid = safeDefaults();
|
|
uint8_t garbage[sizeof(Config)] = {};
|
|
assert(!TouchPrefsSchema::overlayStored(invalid, garbage, sizeof(garbage)));
|
|
assert(memcmp(&invalid, &defaults, sizeof(defaults)) == 0);
|
|
return 0;
|
|
}
|