From e8f271f4ef55e39563a3f887020d30a16f44a8a8 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Mon, 30 Mar 2026 10:00:03 +0200 Subject: [PATCH] feat(path_hash_mode): add hop_count and path_hash_size to API responses Stage 2 of path_hash_mode support. All API endpoints and SocketIO emissions now include decoded hop_count and path_hash_size fields alongside the raw path_len, so the frontend can display and segment paths correctly for any hash mode. Changes: - Import decode_path_len in api.py - GET /api/messages: add hop_count, path_hash_size, echo_hash_sizes - GET /api/messages//meta: add hop_count, path_hash_size, echo_hash_sizes - GET /api/dm/messages: add hop_count, path_hash_size - SocketIO new_message emission: add hop_count, path_hash_size Co-Authored-By: Claude Opus 4.6 --- app/device_manager.py | 12 ++++++++++-- app/routes/api.py | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/app/device_manager.py b/app/device_manager.py index a250f2c..9496945 100644 --- a/app/device_manager.py +++ b/app/device_manager.py @@ -503,9 +503,15 @@ class DeviceManager: if self.socketio: snr = data.get('SNR', data.get('snr')) - path_len = data.get('path_len') + path_len_raw = data.get('path_len') pkt_payload = data.get('pkt_payload') + # Decode path_len into hop_count and path_hash_size + hop_count = None + path_hash_size = 1 + if path_len_raw is not None: + hop_count, path_hash_size, _ = decode_path_len(path_len_raw) + # Compute analyzer URL from pkt_payload analyzer_url = None if pkt_payload: @@ -524,7 +530,9 @@ class DeviceManager: 'timestamp': ts, 'id': msg_id, 'snr': snr, - 'path_len': path_len, + 'path_len': path_len_raw, + 'hop_count': hop_count, + 'path_hash_size': path_hash_size, 'pkt_payload': pkt_payload, 'analyzer_url': analyzer_url, }, namespace='/chat') diff --git a/app/routes/api.py b/app/routes/api.py index b6aeda3..8126b7f 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -18,6 +18,7 @@ from pathlib import Path from flask import Blueprint, jsonify, request, send_file, current_app from app.meshcore import cli, parser from app.config import config, runtime_config +from app.device_manager import decode_path_len from app.archiver import manager as archive_manager from app.contacts_cache import get_all_names, get_all_contacts @@ -412,6 +413,13 @@ def get_messages(): channel_secrets[ch_idx], sender_ts, txt_type, raw_text ) + # Decode path_len into hop_count and path_hash_size + path_len_raw = row.get('path_len') + hop_count = None + path_hash_size = 1 + if path_len_raw is not None: + hop_count, path_hash_size, _ = decode_path_len(path_len_raw) + msg = { 'sender': row.get('sender', ''), 'content': row.get('content', ''), @@ -419,7 +427,9 @@ def get_messages(): 'datetime': datetime.fromtimestamp(row['timestamp']).isoformat() if row.get('timestamp') else None, 'is_own': bool(row.get('is_own', 0)), 'snr': row.get('snr'), - 'path_len': row.get('path_len'), + 'path_len': path_len_raw, + 'hop_count': hop_count, + 'path_hash_size': path_hash_size, 'channel_idx': ch_idx, 'sender_timestamp': sender_ts, 'txt_type': txt_type, @@ -435,6 +445,7 @@ def get_messages(): msg['echo_count'] = len(echoes) msg['echo_paths'] = [e.get('path', '') for e in echoes if e.get('path')] msg['echo_snrs'] = [e.get('snr') for e in echoes if e.get('snr') is not None] + msg['echo_hash_sizes'] = [e.get('hash_size', 1) for e in echoes if e.get('path')] messages.append(msg) @@ -516,10 +527,19 @@ def get_message_meta(msg_id): channel_secrets[ch_idx], sender_ts, txt_type, raw_text ) + # Decode path_len + path_len_raw = row.get('path_len') + hop_count = None + path_hash_size = 1 + if path_len_raw is not None: + hop_count, path_hash_size, _ = decode_path_len(path_len_raw) + meta = { 'success': True, 'snr': row.get('snr'), - 'path_len': row.get('path_len'), + 'path_len': path_len_raw, + 'hop_count': hop_count, + 'path_hash_size': path_hash_size, 'pkt_payload': pkt_payload, } @@ -530,6 +550,7 @@ def get_message_meta(msg_id): meta['echo_count'] = len(echoes) meta['echo_paths'] = [e.get('path', '') for e in echoes if e.get('path')] meta['echo_snrs'] = [e.get('snr') for e in echoes if e.get('snr') is not None] + meta['echo_hash_sizes'] = [e.get('hash_size', 1) for e in echoes if e.get('path')] return jsonify(meta) @@ -1994,6 +2015,13 @@ def get_dm_messages(): db_msgs = db.get_dm_messages(contact_pubkey, limit=limit) messages = [] for row in db_msgs: + # Decode path_len + dm_path_len_raw = row.get('path_len') + dm_hop_count = None + dm_path_hash_size = 1 + if dm_path_len_raw is not None: + dm_hop_count, dm_path_hash_size, _ = decode_path_len(dm_path_len_raw) + messages.append({ 'type': 'dm', 'id': row['id'], @@ -2004,7 +2032,9 @@ def get_dm_messages(): 'datetime': datetime.fromtimestamp(row['timestamp']).isoformat() if row.get('timestamp') else None, 'is_own': row['direction'] == 'out', 'snr': row.get('snr'), - 'path_len': row.get('path_len'), + 'path_len': dm_path_len_raw, + 'hop_count': dm_hop_count, + 'path_hash_size': dm_path_hash_size, 'expected_ack': row.get('expected_ack'), 'delivery_status': row.get('delivery_status'), 'delivery_attempt': row.get('delivery_attempt'),