fix(contacts): decode path correctly using MeshCore V1 encoding

Path buffer from firmware contains trailing garbage bytes beyond the
actual hop data. out_path_len encodes both hop count (lower 6 bits)
and hash size (upper 2 bits). Now we:
- Truncate out_path to meaningful bytes (hop_count * hash_size)
- Format as readable E7→DE→54→54→D8 instead of raw hex string
- Show hop count derived from actual path arrows

Example: out_path_len=5 with out_path="e7de5454d81c49dfb86f8a"
now correctly displays as "E7→DE→54→54→D8 (5 hops)" instead of
showing the full 11-byte buffer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-16 21:26:06 +01:00
parent 5f72f40742
commit 3622619ba4
3 changed files with 30 additions and 18 deletions
+11 -4
View File
@@ -230,12 +230,19 @@ def _execute_console_command(args: list) -> str:
typ = type_names.get(c.get('type', 1), '?')
pk_short = pk[:12]
opl = c.get('out_path_len', -1)
if opl == -1:
path_str = 'Flood'
if opl > 0:
# Decode path: lower 6 bits = hop count, upper 2 bits = hash_size-1
hop_count = opl & 0x3F
hash_size = (opl >> 6) + 1
raw = c.get('out_path', '')
meaningful = raw[:hop_count * hash_size * 2]
chunk = hash_size * 2
hops = [meaningful[i:i+chunk].upper() for i in range(0, len(meaningful), chunk)]
path_str = ''.join(hops) if hops else f'len:{opl}'
elif opl == 0:
path_str = '0 hop'
path_str = 'Direct'
else:
path_str = c.get('out_path', f'len:{opl}')
path_str = 'Flood'
lines.append(f" {name:30} {typ:4} {pk_short} {path_str}")
return f"Contacts ({len(lines)}) on device:\n" + "\n".join(lines)
+14 -8
View File
@@ -2376,17 +2376,23 @@ def get_contacts_detailed_api():
for public_key, details in contacts_detailed.items():
# Compute path display string
# out_path_len encodes hop count (lower 6 bits) and hash_size (upper 2 bits)
# In MeshCore V1: hash_size=1 byte per hop, so each hop = 2 hex chars
out_path_len = details.get('out_path_len', -1)
out_path = details.get('out_path', '')
if out_path:
# out_path present = known route (even if out_path_len says -1)
path_or_mode = out_path
elif out_path_len == -1:
path_or_mode = 'Flood'
out_path_raw = details.get('out_path', '')
if out_path_len > 0 and out_path_raw:
hop_count = out_path_len & 0x3F
hash_size = (out_path_len >> 6) + 1 # 1, 2, or 3 bytes per hop
# Truncate to meaningful bytes (firmware buffer may have trailing garbage)
meaningful_hex = out_path_raw[:hop_count * hash_size * 2]
# Format as HEX→HEX→HEX (each hop is hash_size*2 hex chars)
chunk = hash_size * 2
hops = [meaningful_hex[i:i+chunk].upper() for i in range(0, len(meaningful_hex), chunk)]
path_or_mode = ''.join(hops) if hops else out_path_raw
elif out_path_len == 0:
path_or_mode = '0 hop'
path_or_mode = 'Direct'
else:
path_or_mode = f'Path len: {out_path_len}'
path_or_mode = 'Flood'
contact = {
# All original fields from contact_info
+5 -6
View File
@@ -2148,15 +2148,14 @@ function createExistingContactCard(contact, index) {
pathDiv = document.createElement('div');
pathDiv.className = 'text-muted small';
const mode = contact.path_or_mode || 'Flood';
const pathLen = contact.out_path_len;
if (mode === 'Flood') {
pathDiv.innerHTML = '<i class="bi bi-broadcast"></i> Flood';
} else if (mode === '0 hop') {
pathDiv.innerHTML = '<i class="bi bi-arrow-right-short"></i> Direct (0 hop)';
} else if (mode === 'Direct') {
pathDiv.innerHTML = '<i class="bi bi-arrow-right-short"></i> Direct';
} else {
// mode is hex path string, show hops count + path
const hops = pathLen >= 0 ? pathLen : '?';
pathDiv.innerHTML = `<i class="bi bi-signpost-split"></i> Path: ${mode} (${hops} hops)`;
// mode is formatted path like "E7→DE→54→54→D8"
const hopCount = mode.split('').length;
pathDiv.innerHTML = `<i class="bi bi-signpost-split"></i> ${mode} <span class="text-muted">(${hopCount} hops)</span>`;
}
}