From 91759f531a88ea1aa3f8c8bd7195a37fc2247f0e Mon Sep 17 00:00:00 2001 From: ericz Date: Mon, 18 May 2026 21:30:37 +0200 Subject: [PATCH] fix double entry due to mixed 1byte and 2byte paths --- lib/connector/meshcore_connector.dart | 113 ++++++++++++++++++++++++-- lib/widgets/snr_indicator.dart | 10 +++ 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index ed7d6f12..08cdd121 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -49,14 +49,16 @@ import 'meshcore_protocol.dart'; class DirectRepeater { static const int maxAgeMinutes = 30; // Max age for direct repeater info - final List pubkeyPrefix; - final int pathHashWidth; + List pubkeyPrefix; + int pathHashWidth; + String? contactKeyHex; double snr; DateTime lastUpdated; DirectRepeater({ required List pubkeyPrefix, required this.pathHashWidth, + this.contactKeyHex, required this.snr, DateTime? lastUpdated, }) : pubkeyPrefix = List.unmodifiable(pubkeyPrefix), @@ -79,8 +81,21 @@ class DirectRepeater { return listEquals(pubkeyPrefix, pathBytes.sublist(0, width)); } - void update(double newSNR) { + void update( + double newSNR, { + List? pubkeyPrefix, + int? pathHashWidth, + String? contactKeyHex, + }) { snr = newSNR; + if (pubkeyPrefix != null && + pubkeyPrefix.length >= this.pubkeyPrefix.length) { + this.pubkeyPrefix = List.unmodifiable(pubkeyPrefix); + } + if (pathHashWidth != null && pathHashWidth >= this.pathHashWidth) { + this.pathHashWidth = pathHashWidth; + } + this.contactKeyHex ??= contactKeyHex; lastUpdated = DateTime.now(); } @@ -6239,6 +6254,25 @@ class MeshCoreConnector extends ChangeNotifier { 0, math.min(hashWidth, contact.publicKey.length), ); + final contactKeyHex = _resolveDirectRepeaterContactKeyHex( + contact, + pubkeyPrefix, + path.isEmpty, + ); + final knownRepeaters = contactKeyHex == null + ? _directRepeaters + .where( + (r) => + r.contactKeyHex != null && + _contactKeyMatchesPrefix(r.contactKeyHex!, pubkeyPrefix), + ) + .toList() + : const []; + final knownRepeater = knownRepeaters.length == 1 + ? knownRepeaters.first + : null; + final effectiveContactKeyHex = + contactKeyHex ?? knownRepeater?.contactKeyHex; _directRepeaters.removeWhere((r) => r.isStale()); @@ -6249,9 +6283,25 @@ class MeshCoreConnector extends ChangeNotifier { return; } - final isTracked = _directRepeaters.where( - (r) => r.pathHashWidth == hashWidth && r.matchesPrefix(pubkeyPrefix), - ); + final isTracked = _directRepeaters.where((r) { + if (knownRepeater != null) { + return identical(r, knownRepeater); + } + if (effectiveContactKeyHex != null) { + return r.contactKeyHex == effectiveContactKeyHex || + (r.contactKeyHex == null && + _contactKeyMatchesPrefix( + effectiveContactKeyHex, + r.pubkeyPrefix, + )); + } + if (r.contactKeyHex == null && + r.pathHashWidth == hashWidth && + r.matchesPrefix(pubkeyPrefix)) { + return true; + } + return false; + }).toList(); final sortedRepeaters = List.from(_directRepeaters) ..sort((a, b) => b.snr.compareTo(a.snr)); @@ -6267,12 +6317,30 @@ class MeshCoreConnector extends ChangeNotifier { if (isTracked.isNotEmpty) { final repeater = isTracked.first; - repeater.update(snr); + repeater.update( + snr, + pubkeyPrefix: pubkeyPrefix, + pathHashWidth: hashWidth, + contactKeyHex: effectiveContactKeyHex, + ); + if (effectiveContactKeyHex != null) { + _directRepeaters.removeWhere( + (r) => + !identical(r, repeater) && + (r.contactKeyHex == effectiveContactKeyHex || + (r.contactKeyHex == null && + _contactKeyMatchesPrefix( + effectiveContactKeyHex, + r.pubkeyPrefix, + ))), + ); + } } else if (_directRepeaters.length < 5) { _directRepeaters.add( DirectRepeater( pubkeyPrefix: pubkeyPrefix, pathHashWidth: hashWidth, + contactKeyHex: effectiveContactKeyHex, snr: snr, ), ); @@ -6280,6 +6348,37 @@ class MeshCoreConnector extends ChangeNotifier { notifyListeners(); } + String? _resolveDirectRepeaterContactKeyHex( + Contact contact, + List pubkeyPrefix, + bool pathIsEmpty, + ) { + if (pathIsEmpty && + (contact.type == advTypeRepeater || contact.type == advTypeRoom)) { + return contact.publicKeyHex; + } + + final prefixMatches = allContacts + .where( + (c) => + (c.type == advTypeRepeater || c.type == advTypeRoom) && + _contactKeyMatchesPrefix(c.publicKeyHex, pubkeyPrefix), + ) + .toList(); + if (prefixMatches.length == 1) { + return prefixMatches.first.publicKeyHex; + } + + return null; + } + + bool _contactKeyMatchesPrefix(String contactKeyHex, List pubkeyPrefix) { + final prefixHex = pubkeyPrefix + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(); + return contactKeyHex.startsWith(prefixHex); + } + void _handleAutoAddConfig(Uint8List frame) { final reader = BufferReader(frame); try { diff --git a/lib/widgets/snr_indicator.dart b/lib/widgets/snr_indicator.dart index 4850393f..ca8e55ce 100644 --- a/lib/widgets/snr_indicator.dart +++ b/lib/widgets/snr_indicator.dart @@ -12,9 +12,18 @@ import 'signal_ui.dart'; Contact? _getRepeaterPrefixMatchNearLocation( List contacts, List pubkeyPrefix, { + String? contactKeyHex, LatLng? searchPoint, bool preferFavorites = false, }) { + if (contactKeyHex != null) { + for (final c in contacts) { + if (c.publicKeyHex == contactKeyHex) { + return c; + } + } + } + final candidates = contacts .where( (c) => @@ -242,6 +251,7 @@ class _SNRIndicatorState extends State { final contact = _getRepeaterPrefixMatchNearLocation( allContacts, repeater.pubkeyPrefix, + contactKeyHex: repeater.contactKeyHex, searchPoint: selfPoint, preferFavorites: true, );