From fecf8cdccbd9708909f2542ed8367a1c2d6127de Mon Sep 17 00:00:00 2001 From: MarekWo Date: Fri, 5 Jun 2026 08:29:33 +0200 Subject: [PATCH] fix(console): multi-byte hops in change_path parser and path display The console treated 2-/3-byte hops as 1-byte: - change_path "" d103 5e34 (space-separated) was joined into continuous hex with hash_size=1, producing four 1-byte hops instead of two 2-byte ones. - path always rendered 1-byte hops because it decoded the hash-size mode from upper bits of out_path_len. In meshcore 2.x the library already masks out_path_len to the hop count and exposes the mode separately in out_path_hash_mode. Parser now splits on commas, whitespace, or arrow separators and requires consistent hop length. Display reads out_path_hash_mode and also shows the byte size, e.g. "D103,5E34 (2 hops, 2B)". --- app/device_manager.py | 2 +- app/main.py | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/device_manager.py b/app/device_manager.py index c2b143b..07fa64e 100644 --- a/app/device_manager.py +++ b/app/device_manager.py @@ -2753,7 +2753,7 @@ class DeviceManager: return {'success': True, 'data': { 'out_path': contact.get('out_path', ''), 'out_path_len': contact.get('out_path_len', -1), - 'out_path_hash_len': contact.get('out_path_hash_len', 0), + 'out_path_hash_mode': contact.get('out_path_hash_mode', 0), }} def discover_path(self, name_or_key: str) -> Dict: diff --git a/app/main.py b/app/main.py index 0fb4499..094c0e0 100644 --- a/app/main.py +++ b/app/main.py @@ -807,14 +807,15 @@ def _execute_console_command(args: list) -> str: data = result['data'] opl = data.get('out_path_len', -1) raw = data.get('out_path', '') + hash_mode = data.get('out_path_hash_mode', 0) if opl > 0: - hop_count = opl & 0x3F - hash_size = (opl >> 6) + 1 - meaningful = raw[:hop_count * hash_size * 2] + hop_count = opl + hash_size = max(1, hash_mode + 1) if hash_mode >= 0 else 1 chunk = hash_size * 2 + meaningful = raw[:hop_count * chunk] hops = [meaningful[i:i+chunk].upper() for i in range(0, len(meaningful), chunk)] path_str = ','.join(hops) if hops else f'len:{opl}' - return f"Path to {name}: {path_str} ({hop_count} hops)" + return f"Path to {name}: {path_str} ({hop_count} hops, {hash_size}B)" elif opl == 0: return f"Path to {name}: Direct" else: @@ -852,17 +853,23 @@ def _execute_console_command(args: list) -> str: if result.get('success'): return result.get('message', 'OK') return f"Error: {result.get('error')}" - if ',' in raw: - chunks = [c.strip() for c in raw.split(',') if c.strip()] - first_len = len(chunks[0]) if chunks else 0 - if first_len in (2, 4, 6): - hash_size = first_len // 2 - else: + # Split on commas, whitespace, or arrow separators. Each chunk = one hop. + norm = raw.replace('→', ' ').replace('->', ' ') + chunks = [c for c in re.split(r'[,\s]+', norm) if c] + if len(chunks) >= 2: + first_len = len(chunks[0]) + if first_len not in (2, 4, 6): return "Error: hop must be 1, 2, or 3 bytes (2/4/6 hex chars)" + if any(len(c) != first_len for c in chunks): + return "Error: all hops must have the same length" + hash_size = first_len // 2 path_hex = ''.join(chunks) - else: - path_hex = raw.replace(' ', '').replace('→', '').replace('->', '') + elif len(chunks) == 1: + # Single chunk: continuous hex, assume 1-byte hops + path_hex = chunks[0] hash_size = 1 + else: + return "Error: empty path" try: bytes.fromhex(path_hex) except ValueError: