mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 02:11:57 +00:00
- Removing a scope moves the channels that used it to the default, not to unscoped. - The room bot ignores posts older than 5 minutes: the backlog a room replays after login no longer gets answers. - Adverts take the default scope from the scope list, as messages do; the legacy fields always mirror it, and CMD_SET_DEFAULT_FLOOD_SCOPE with a full list answers TABLE_FULL. - The last DM resend drops the direct path and floods. - /trail and /waypoints are written through a temp file. - An admin login timeout keeps the saved password. - History records read from the SD card are sanitised. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
5.8 KiB
C
133 lines
5.8 KiB
C
#pragma once
|
|
// The shared scope list: the primary / default scope, per-channel scopes and
|
|
// the repeat scopes they make.
|
|
// Not part of upstream MyMesh.cpp: included at its bottom, after all class
|
|
// definitions (as MyMeshBot.h and MyMeshCli.h).
|
|
|
|
bool MyMesh::setPrimaryScope(const char* name) {
|
|
bool ok = true;
|
|
if (!name || name[0] == '\0') {
|
|
_scope_list.default_idx = 0; // "*"
|
|
} else {
|
|
// Find-or-create a matching named entry (e.g. the app driving this
|
|
// remotely via CMD_SET_DEFAULT_FLOOD_SCOPE), then mark it default.
|
|
char nm[sizeof(_prefs.default_scope_name)];
|
|
StrHelper::strncpy(nm, name, sizeof(nm));
|
|
uint8_t idx = 0;
|
|
for (uint8_t i = 0; i < _scope_list.count; i++) {
|
|
if (strcmp(_scope_list.entries[i].name, nm) == 0) { idx = i + 1; break; }
|
|
}
|
|
if (idx == 0) idx = _scope_list.add(nm);
|
|
// add() returns 0 only when the list is full. Keep the previous default
|
|
// rather than taking that as "*": silently dropping to unscoped would put
|
|
// traffic on the air outside any region, which is a louder failure than
|
|
// ignoring a request we had no room to honour.
|
|
if (idx != 0) _scope_list.default_idx = idx;
|
|
else ok = false;
|
|
}
|
|
// The legacy fields always mirror the list's default, so a refused request
|
|
// can't leave them naming a scope nothing else sends with.
|
|
syncLegacyDefaultScope();
|
|
if (_store) _store->saveScopeList(_scope_list);
|
|
rebuildRepeatScopes();
|
|
return ok;
|
|
}
|
|
|
|
void MyMesh::syncLegacyDefaultScope() {
|
|
uint8_t idx = _scope_list.default_idx;
|
|
if (idx == 0) { // "*" -- no default scope, same as the field never being set
|
|
memset(_prefs.default_scope_name, 0, sizeof(_prefs.default_scope_name));
|
|
memset(_prefs.default_scope_key, 0, sizeof(_prefs.default_scope_key));
|
|
} else {
|
|
const ScopeEntry& e = _scope_list.entries[idx - 1];
|
|
StrHelper::strncpy(_prefs.default_scope_name, e.name, sizeof(_prefs.default_scope_name));
|
|
memcpy(_prefs.default_scope_key, e.key, sizeof(_prefs.default_scope_key));
|
|
}
|
|
}
|
|
|
|
uint8_t MyMesh::addScope(const char* name) {
|
|
uint8_t idx = _scope_list.add(name);
|
|
if (idx && _store) _store->saveScopeList(_scope_list);
|
|
return idx;
|
|
}
|
|
|
|
void MyMesh::renameScope(uint8_t idx, const char* name) {
|
|
if (idx < 1 || idx > _scope_list.count || !name || !name[0]) return;
|
|
ScopeEntry& e = _scope_list.entries[idx - 1];
|
|
StrHelper::strncpy(e.name, name, sizeof(e.name));
|
|
ScopeList::deriveKey(e.name, e.key);
|
|
if (_store) _store->saveScopeList(_scope_list);
|
|
if (_scope_list.default_idx == idx) { // renaming the default changes its key too
|
|
syncLegacyDefaultScope();
|
|
savePrefs();
|
|
}
|
|
rebuildRepeatScopes(); // this entry's key may be repeat_scopes[]'s default or an extra slot
|
|
}
|
|
|
|
void MyMesh::removeScope(uint8_t idx) {
|
|
if (idx < 1 || idx > _scope_list.count) return;
|
|
_scope_list.remove(idx);
|
|
// repeat_extra_scope_mask bit (i) tracks list index (i+1) -- shift down the
|
|
// same way ScopeList::remove() shifted entries[], dropping the removed bit.
|
|
uint16_t old_mask = _prefs.repeat_extra_scope_mask, new_mask = 0;
|
|
for (uint8_t i = 0; i < ScopeList::MAX_SCOPE_ENTRIES; i++) { // one bit per possible list entry, not per MAX_REPEAT_SCOPES active slot
|
|
if (!(old_mask & (1u << i))) continue;
|
|
uint8_t list_idx = i + 1;
|
|
if (list_idx < idx) new_mask |= (1u << i);
|
|
else if (list_idx > idx) new_mask |= (1u << (i - 1));
|
|
// list_idx == idx: the removed one, dropped
|
|
}
|
|
_prefs.repeat_extra_scope_mask = new_mask;
|
|
// Any channel pointed at the removed entry (or shifted ones) needs the same
|
|
// fix-up ScopeList::remove() applied to default_idx.
|
|
for (uint8_t i = 0; i < NodePrefs::MAX_SCOPED_CHANNELS; i++) {
|
|
uint8_t ci = _prefs.ch_scope_idx[i];
|
|
if (ci == NodePrefs::CH_SCOPE_DEFAULT) continue; // follows the default, fixed up above
|
|
if (ci == idx) _prefs.ch_scope_idx[i] = NodePrefs::CH_SCOPE_DEFAULT; // back to the default, not to unscoped "*"
|
|
else if (ci > idx) _prefs.ch_scope_idx[i] = ci - 1;
|
|
}
|
|
// Live Share's own scope (stored as list index + 1; 0 = follow the target).
|
|
if (_prefs.loc_share_scope) {
|
|
uint8_t li = _prefs.loc_share_scope - 1;
|
|
if (li == idx) _prefs.loc_share_scope = 0;
|
|
else if (li > idx) _prefs.loc_share_scope--;
|
|
}
|
|
syncLegacyDefaultScope(); // ScopeList::remove() may have moved or cleared the default
|
|
if (_store) _store->saveScopeList(_scope_list);
|
|
// The fix-ups above live in NodePrefs, not in /scopes1, so both files have to
|
|
// be written here. Saving only the list would leave the two out of step after
|
|
// a reboot: the entries would be shifted down but every channel's saved index
|
|
// (and the repeater's mask) would still point at the pre-delete positions, so
|
|
// they'd silently resolve to the wrong scope -- the same class of bug the
|
|
// stray-bits clamp in DataStore guards against, just from the other side.
|
|
savePrefs();
|
|
rebuildRepeatScopes();
|
|
}
|
|
|
|
void MyMesh::setDefaultScope(uint8_t idx) {
|
|
_scope_list.default_idx = _scope_list.clamp(idx);
|
|
syncLegacyDefaultScope(); // so CMD_GET_DEFAULT_FLOOD_SCOPE answers with what we now use
|
|
if (_store) _store->saveScopeList(_scope_list);
|
|
savePrefs();
|
|
rebuildRepeatScopes();
|
|
}
|
|
|
|
void MyMesh::setChannelScope(uint8_t channel_idx, uint8_t idx) {
|
|
if (channel_idx >= NodePrefs::MAX_SCOPED_CHANNELS) return;
|
|
_prefs.ch_scope_idx[channel_idx] = idx == NodePrefs::CH_SCOPE_DEFAULT ? idx : _scope_list.clamp(idx);
|
|
}
|
|
|
|
void MyMesh::rebuildRepeatScopes() {
|
|
repeat_scope_count = 0;
|
|
|
|
TransportKey primary = _scope_list.key(_scope_list.default_idx);
|
|
if (!primary.isNull()) repeat_scopes[repeat_scope_count++] = primary;
|
|
|
|
for (uint8_t i = 0; i < _scope_list.count && repeat_scope_count < MAX_REPEAT_SCOPES; i++) {
|
|
if (!(_prefs.repeat_extra_scope_mask & (1u << i))) continue;
|
|
uint8_t list_idx = i + 1;
|
|
TransportKey k = _scope_list.key(list_idx);
|
|
if (!k.isNull()) repeat_scopes[repeat_scope_count++] = k;
|
|
}
|
|
}
|