fix(companion): dedup node-discover responses heard more than once

A discover response often arrives twice — the zero-hop direct copy and a
re-flooded copy relayed by another repeater carry different packet hashes,
so the mesh duplicate filter passes both. The standalone scan appended the
same node twice and app-driven discover forwarded both copies, so one
repeater showed up as two.

Track (tag, responder pubkey) for a short window in isDupDiscoverResp() and
drop the second copy in both the standalone and app-forward paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-05 17:00:52 +02:00
parent 9a71ee14b6
commit 399b61e2bb
2 changed files with 47 additions and 0 deletions

View File

@@ -1169,6 +1169,28 @@ MyMesh::PingResult* MyMesh::getPingResult(uint32_t tag) {
return NULL;
}
// True when this (tag, responder) pair was already seen recently; records it
// otherwise. A discover response is often heard more than once — the zero-hop
// direct copy and a re-flooded copy relayed by another repeater carry
// different packet hashes, so the mesh duplicate filter passes both. Without
// this, the standalone scan appended the same node twice and an app-driven
// discover had both copies forwarded, so the app listed one repeater as two.
bool MyMesh::isDupDiscoverResp(uint32_t tag, const uint8_t* pub_key) {
for (int i = 0; i < DISCOVER_SEEN_MAX; i++) {
DiscoverSeen& e = _disc_seen[i];
if (e.until != 0 && !millisHasNowPassed(e.until)
&& e.tag == tag && memcmp(e.pk, pub_key, sizeof(e.pk)) == 0) {
return true;
}
}
DiscoverSeen& s = _disc_seen[_disc_seen_head];
_disc_seen_head = (_disc_seen_head + 1) % DISCOVER_SEEN_MAX;
s.tag = tag;
memcpy(s.pk, pub_key, sizeof(s.pk));
s.until = futureMillis(10000); // copies of one response arrive within seconds
return false;
}
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
// If we have an active standalone discover, check if this is a matching response.
// Tag matching provides isolation — no isBLEConnected check needed.
@@ -1181,6 +1203,7 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
if (tag == _pending_node_discover_tag) {
uint8_t node_type = packet->payload[0] & 0x0F;
const uint8_t* pub_key = &packet->payload[6];
if (isDupDiscoverResp(tag, pub_key)) return; // second copy of the same response
ContactInfo* known = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
if (known) {
known->lastmod = getRTCClock()->getCurrentTime();
@@ -1207,6 +1230,16 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
}
}
// App-driven discover: drop duplicate copies of the same response before
// forwarding, so the app doesn't list one responder twice. (Our own
// standalone discover already returned above on a tag match.)
if ((packet->payload[0] & 0xF0) == CTL_TYPE_NODE_DISCOVER_RESP &&
packet->payload_len >= 6 + PUB_KEY_SIZE) {
uint32_t fwd_tag;
memcpy(&fwd_tag, &packet->payload[2], 4);
if (isDupDiscoverResp(fwd_tag, &packet->payload[6])) return;
}
if (packet->payload_len + 4 > sizeof(out_frame)) {
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
return;
@@ -1478,6 +1511,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_discover_count = 0;
_pending_node_discover_tag = 0;
_pending_node_discover_until = 0;
memset(_disc_seen, 0, sizeof(_disc_seen));
_disc_seen_head = 0;
// Ping state
_ping_callback = NULL;

View File

@@ -405,6 +405,18 @@ private:
uint32_t _pending_node_discover_tag;
unsigned long _pending_node_discover_until;
// Dedup for NODE_DISCOVER_RESP copies heard more than once: the responder's
// zero-hop direct copy and a re-flooded copy relayed by another repeater
// carry different packet hashes, so the mesh duplicate filter passes both.
// Keyed by (tag, responder pubkey prefix) with a short expiry — covers both
// the standalone on-device scan and responses forwarded to the app (which
// would otherwise list the same repeater twice).
#define DISCOVER_SEEN_MAX 16
struct DiscoverSeen { uint32_t tag; uint8_t pk[6]; unsigned long until; };
DiscoverSeen _disc_seen[DISCOVER_SEEN_MAX];
uint8_t _disc_seen_head;
bool isDupDiscoverResp(uint32_t tag, const uint8_t* pub_key); // records when new
// ── Ping/Trace state ──────────────────────────────────────────────────────
PingResult _ping_results[PING_RESULT_MAX];
PingCallback _ping_callback;