mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-06 04:06:12 +00:00
feat(diag): reset-counters popup; harden repeater loop-detect bounds
- DiagnosticsScreen: Enter opens a confirm popup (defaults to Cancel) that zeroes the cumulative counters via Mesh::resetStats(). - Dispatcher::resetStats() made virtual; Mesh overrides it to also clear n_forwarded so the reset covers the repeater forward count. - REPEAT_LOOP_MAX gains a 4-byte-hash entry and isRepeatLooped() bounds- checks hash_size, so it can't OOB-read if path_mode 3 is ever accepted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d1d64dc612
commit
f55b869fbe
@@ -525,7 +525,11 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
|||||||
// thresholds (max times this node's hash may already appear in the path, by hash size).
|
// thresholds (max times this node's hash may already appear in the path, by hash size).
|
||||||
// A companion moves around, so it re-enters its own flood's path more easily than a
|
// A companion moves around, so it re-enters its own flood's path more easily than a
|
||||||
// fixed repeater — hardcoded rather than configurable since there's no CLI here.
|
// fixed repeater — hardcoded rather than configurable since there's no CLI here.
|
||||||
static const uint8_t REPEAT_LOOP_MAX[] = { 0, /*1-byte*/ 2, /*2-byte*/ 1, /*3-byte*/ 1 };
|
// Indexed by getPathHashSize() = (path_len>>6)+1, so 1..4. Index 0 is unused
|
||||||
|
// (hash size is never 0); index 4 covers path_mode 3, which tryParsePacket
|
||||||
|
// currently rejects — kept in-bounds so this can't OOB-read if that guard is
|
||||||
|
// ever relaxed.
|
||||||
|
static const uint8_t REPEAT_LOOP_MAX[] = { 0, /*1-byte*/ 2, /*2-byte*/ 1, /*3-byte*/ 1, /*4-byte*/ 1 };
|
||||||
// Caps how many hops an ADVERT flood gets repeated, matching simple_repeater's default
|
// Caps how many hops an ADVERT flood gets repeated, matching simple_repeater's default
|
||||||
// flood_max_advert — adverts are the most frequent flood traffic, so this is the one
|
// flood_max_advert — adverts are the most frequent flood traffic, so this is the one
|
||||||
// depth limit worth keeping even without the rest of simple_repeater's flood_max knobs.
|
// depth limit worth keeping even without the rest of simple_repeater's flood_max knobs.
|
||||||
@@ -533,6 +537,7 @@ static const uint8_t REPEAT_MAX_ADVERT_HOPS = 8;
|
|||||||
|
|
||||||
bool MyMesh::isRepeatLooped(const mesh::Packet* packet) const {
|
bool MyMesh::isRepeatLooped(const mesh::Packet* packet) const {
|
||||||
uint8_t hash_size = packet->getPathHashSize();
|
uint8_t hash_size = packet->getPathHashSize();
|
||||||
|
if (hash_size >= sizeof(REPEAT_LOOP_MAX)) return true; // unknown hash size: treat as looped, don't forward
|
||||||
uint8_t hash_count = packet->getPathHashCount();
|
uint8_t hash_count = packet->getPathHashCount();
|
||||||
uint8_t n = 0;
|
uint8_t n = 0;
|
||||||
const uint8_t* path = packet->path;
|
const uint8_t* path = packet->path;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <helpers/DeviceDiag.h>
|
#include <helpers/DeviceDiag.h>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "icons.h"
|
#include "icons.h"
|
||||||
|
#include "PopupMenu.h"
|
||||||
#include "../MyMesh.h"
|
#include "../MyMesh.h"
|
||||||
|
|
||||||
extern MyMesh the_mesh;
|
extern MyMesh the_mesh;
|
||||||
@@ -18,6 +19,7 @@ extern MyMesh the_mesh;
|
|||||||
class DiagnosticsScreen : public UIScreen {
|
class DiagnosticsScreen : public UIScreen {
|
||||||
UITask* _task;
|
UITask* _task;
|
||||||
int _scroll = 0;
|
int _scroll = 0;
|
||||||
|
PopupMenu _reset_menu; // Enter → confirm before zeroing the cumulative counters
|
||||||
|
|
||||||
struct Row { const char* label; char value[20]; };
|
struct Row { const char* label; char value[20]; };
|
||||||
static const int MAX_ROWS = 13;
|
static const int MAX_ROWS = 13;
|
||||||
@@ -128,13 +130,31 @@ public:
|
|||||||
}
|
}
|
||||||
drawScrollIndicator(display, start_y, visible * item_h, _row_count, visible, _scroll);
|
drawScrollIndicator(display, start_y, visible * item_h, _row_count, visible, _scroll);
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
return 1000; // stats refresh once a second — no need for a faster redraw
|
if (_reset_menu.active) _reset_menu.render(display);
|
||||||
|
return _reset_menu.active ? 50 : 1000; // popup wants snappier redraw; stats otherwise refresh once a second
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleInput(char c) override {
|
bool handleInput(char c) override {
|
||||||
|
if (_reset_menu.active) {
|
||||||
|
auto res = _reset_menu.handleInput(c);
|
||||||
|
// Index 0 = "Reset", 1 = "Cancel" (destructive option first, but never the
|
||||||
|
// default landing row — see setSelected below).
|
||||||
|
if (res == PopupMenu::SELECTED && _reset_menu.selectedIndex() == 0) {
|
||||||
|
the_mesh.resetStats(); // zeroes Dispatcher per-type counters + Mesh forward count
|
||||||
|
_task->showAlert("Counters reset", 800);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (c == KEY_UP) { if (_scroll > 0) _scroll--; return true; }
|
if (c == KEY_UP) { if (_scroll > 0) _scroll--; return true; }
|
||||||
if (c == KEY_DOWN) { _scroll++; return true; } // clamped to max_scroll in render()
|
if (c == KEY_DOWN) { _scroll++; return true; } // clamped to max_scroll in render()
|
||||||
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU || c == KEY_ENTER) { _task->gotoToolsScreen(); return true; }
|
if (c == KEY_ENTER) {
|
||||||
|
_reset_menu.begin("Reset counters?", 2);
|
||||||
|
_reset_menu.addItem("Reset");
|
||||||
|
_reset_menu.addItem("Cancel");
|
||||||
|
_reset_menu.setSelected(1); // default to Cancel so a stray Enter doesn't wipe stats
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoToolsScreen(); return true; }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -189,7 +189,7 @@ public:
|
|||||||
uint32_t getNumRecvByType(uint8_t payload_type) const { return n_recv_by_type[payload_type & 0x0F]; }
|
uint32_t getNumRecvByType(uint8_t payload_type) const { return n_recv_by_type[payload_type & 0x0F]; }
|
||||||
int getPoolFreeCount() const { return _mgr->getFreeCount(); }
|
int getPoolFreeCount() const { return _mgr->getFreeCount(); }
|
||||||
int getOutboundQueueLen() const { return _mgr->getOutboundTotal(); }
|
int getOutboundQueueLen() const { return _mgr->getOutboundTotal(); }
|
||||||
void resetStats() {
|
virtual void resetStats() {
|
||||||
n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0;
|
n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0;
|
||||||
memset(n_sent_by_type, 0, sizeof(n_sent_by_type));
|
memset(n_sent_by_type, 0, sizeof(n_sent_by_type));
|
||||||
memset(n_recv_by_type, 0, sizeof(n_recv_by_type));
|
memset(n_recv_by_type, 0, sizeof(n_recv_by_type));
|
||||||
|
|||||||
@@ -183,6 +183,9 @@ public:
|
|||||||
// confirms a repeater/transport role is really forwarding, not just configured to.
|
// confirms a repeater/transport role is really forwarding, not just configured to.
|
||||||
uint32_t getNumForwarded() const { return n_forwarded; }
|
uint32_t getNumForwarded() const { return n_forwarded; }
|
||||||
|
|
||||||
|
// also clear the forward counter alongside the Dispatcher packet counters.
|
||||||
|
void resetStats() override { Dispatcher::resetStats(); n_forwarded = 0; }
|
||||||
|
|
||||||
RNG* getRNG() const { return _rng; }
|
RNG* getRNG() const { return _rng; }
|
||||||
RTCClock* getRTCClock() const { return _rtc; }
|
RTCClock* getRTCClock() const { return _rtc; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user