Enable route hash settings

This commit is contained in:
Janez T
2026-03-08 17:00:54 +01:00
parent 6b2f18130c
commit 9f62055eef
4 changed files with 212 additions and 66 deletions

View File

@@ -4,38 +4,49 @@ import '../models/contact.dart';
class DecodedLogRxRoute {
final int payloadType;
final List<int> pathHashes;
final List<int> pathBytes;
final int hashSize;
const DecodedLogRxRoute({
required this.payloadType,
required this.pathHashes,
required this.pathBytes,
required this.hashSize,
});
int? get originalSenderHash => pathHashes.isEmpty ? null : pathHashes.first;
List<String> get hopHashes =>
LogRxRouteDecoder.splitHopHashes(pathBytes, hashSize: hashSize);
int get hopCount => hopHashes.length;
String? get originalSenderHashHex =>
hopHashes.isEmpty ? null : hopHashes.first;
}
class ResolvedNodeHash {
final int hash;
final String hashHex;
final String label;
final bool isOwnNode;
final bool isUniqueMatch;
final int matchCount;
const ResolvedNodeHash({
required this.hash,
required this.hashHex,
required this.label,
required this.isOwnNode,
required this.isUniqueMatch,
required this.matchCount,
});
String get hexLabel => '0x${hash.toRadixString(16).padLeft(2, '0')}';
String get hexLabel => '0x${hashHex.toUpperCase()}';
}
class LogRxRouteDecoder {
const LogRxRouteDecoder._();
static DecodedLogRxRoute? decode(Uint8List rawData) {
static DecodedLogRxRoute? decode(
Uint8List rawData, {
int? preferredHashSize,
}) {
if (rawData.length < 5 || rawData[0] != 0x88) return null;
final rawPacketData = rawData.sublist(3);
@@ -54,28 +65,79 @@ class LogRxRouteDecoder {
if (rawPacketData.length <= index) return null;
final pathLen = rawPacketData[index++];
if (rawPacketData.length < index + pathLen) return null;
final pathBytes = rawPacketData.sublist(index, index + pathLen);
final hashSize = inferHashSize(
pathBytes,
preferredHashSize: preferredHashSize,
);
return DecodedLogRxRoute(
payloadType: payloadType,
pathHashes: rawPacketData.sublist(index, index + pathLen),
pathBytes: pathBytes,
hashSize: hashSize,
);
}
static int inferHashSize(List<int> pathBytes, {int? preferredHashSize}) {
if (pathBytes.isEmpty) return 1;
final normalizedPreferred =
preferredHashSize != null &&
preferredHashSize >= 1 &&
preferredHashSize <= 3
? preferredHashSize
: null;
if (normalizedPreferred != null &&
pathBytes.length % normalizedPreferred == 0) {
return normalizedPreferred;
}
for (final candidate in const [3, 2, 1]) {
if (pathBytes.length % candidate == 0) {
return candidate;
}
}
return 1;
}
static List<String> splitHopHashes(
List<int> pathBytes, {
required int hashSize,
}) {
if (pathBytes.isEmpty) return const [];
if (hashSize < 1 || hashSize > 3 || pathBytes.length % hashSize != 0) {
return pathBytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.toList();
}
final hops = <String>[];
for (var index = 0; index < pathBytes.length; index += hashSize) {
hops.add(
pathBytes
.sublist(index, index + hashSize)
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join(),
);
}
return hops;
}
static ResolvedNodeHash resolveHash(
int hash, {
String hashHex, {
required Iterable<Contact> contacts,
Uint8List? ownPublicKey,
String? ownName,
}) {
final ownHash = ownPublicKey != null && ownPublicKey.isNotEmpty
? ownPublicKey.first
: null;
if (ownHash == hash) {
final normalizedHashHex = hashHex.toLowerCase();
final ownKeyHex = _bytesToHex(ownPublicKey);
if (ownKeyHex != null && ownKeyHex.startsWith(normalizedHashHex)) {
final ownLabel = (ownName != null && ownName.trim().isNotEmpty)
? '$ownName (you)'
: 'You';
return ResolvedNodeHash(
hash: hash,
hashHex: normalizedHashHex,
label: ownLabel,
isOwnNode: true,
isUniqueMatch: true,
@@ -84,12 +146,12 @@ class LogRxRouteDecoder {
}
final matches = contacts.where((contact) {
return contact.publicKey.isNotEmpty && contact.publicKey.first == hash;
return contact.publicKeyHex.toLowerCase().startsWith(normalizedHashHex);
}).toList();
if (matches.isEmpty) {
return ResolvedNodeHash(
hash: hash,
hashHex: normalizedHashHex,
label: 'Unknown',
isOwnNode: false,
isUniqueMatch: false,
@@ -99,7 +161,7 @@ class LogRxRouteDecoder {
if (matches.length == 1) {
return ResolvedNodeHash(
hash: hash,
hashHex: normalizedHashHex,
label: matches.first.displayName,
isOwnNode: false,
isUniqueMatch: true,
@@ -119,11 +181,19 @@ class LogRxRouteDecoder {
? '$candidateNames +$extraCount'
: candidateNames;
return ResolvedNodeHash(
hash: hash,
hashHex: normalizedHashHex,
label: label,
isOwnNode: false,
isUniqueMatch: false,
matchCount: matches.length,
);
}
static String? _bytesToHex(Uint8List? bytes) {
if (bytes == null || bytes.isEmpty) return null;
return bytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join()
.toLowerCase();
}
}