mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-07 09:12:57 +02:00
feat(path_hash_mode): add decode_path_len and fix RX_LOG_DATA parsing
Stage 1 of path_hash_mode support. The critical bug in _on_rx_log_data treated the raw path_len byte as a direct byte count, which breaks with mode>0 (e.g. mode=1, 0 hops → path_len=0x40=64, reading 64 bytes of non-existent path data). Now properly decodes the encoded path_len byte into hop_count, hash_size, and path_byte_len. Changes: - Add decode_path_len() utility for MeshCore v1.14+ path_len encoding - Fix _on_rx_log_data binary parsing to use decoded path length - Pass hash_size through _process_echo → DB insert → SocketIO emission - Add hash_size column to echoes table (schema + migration) - Update insert_echo() to store hash_size (default 1 for backward compat) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+10
-3
@@ -56,6 +56,12 @@ class Database:
|
||||
conn.execute(f"ALTER TABLE direct_messages ADD COLUMN {col} {typedef}")
|
||||
logger.info(f"Migration: added direct_messages.{col} column")
|
||||
|
||||
# Add hash_size column to echoes (path_hash_mode support)
|
||||
echo_columns = {r[1] for r in conn.execute("PRAGMA table_info(echoes)").fetchall()}
|
||||
if 'hash_size' not in echo_columns:
|
||||
conn.execute("ALTER TABLE echoes ADD COLUMN hash_size INTEGER NOT NULL DEFAULT 1")
|
||||
logger.info("Migration: added echoes.hash_size column")
|
||||
|
||||
@contextmanager
|
||||
def _connect(self):
|
||||
"""Yield a connection with auto-commit/rollback."""
|
||||
@@ -768,13 +774,14 @@ class Database:
|
||||
def insert_echo(self, pkt_payload: str, **kwargs) -> None:
|
||||
with self._connect() as conn:
|
||||
conn.execute(
|
||||
"""INSERT INTO echoes (pkt_payload, path, snr, direction, cm_id)
|
||||
VALUES (?, ?, ?, ?, ?)""",
|
||||
"""INSERT INTO echoes (pkt_payload, path, snr, direction, cm_id, hash_size)
|
||||
VALUES (?, ?, ?, ?, ?, ?)""",
|
||||
(pkt_payload,
|
||||
kwargs.get('path'),
|
||||
kwargs.get('snr'),
|
||||
kwargs.get('direction', 'incoming'),
|
||||
kwargs.get('cm_id'))
|
||||
kwargs.get('cm_id'),
|
||||
kwargs.get('hash_size', 1))
|
||||
)
|
||||
|
||||
def get_echoes_for_message(self, pkt_payload: str) -> List[Dict]:
|
||||
|
||||
+27
-5
@@ -22,6 +22,24 @@ logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def decode_path_len(path_len_raw: int) -> tuple:
|
||||
"""Decode the path_len byte (MeshCore v1.14+ encoding).
|
||||
|
||||
Bits 7-6: hash_size - 1 (00=1B, 01=2B, 10=3B, 11=reserved/direct)
|
||||
Bits 5-0: hop_count (0-63)
|
||||
|
||||
Special case: 0xFF = direct routing (not flood) — returns (0, 1, 0).
|
||||
|
||||
Returns:
|
||||
(hop_count, hash_size, path_byte_len)
|
||||
"""
|
||||
if path_len_raw == 0xFF:
|
||||
return 0, 1, 0
|
||||
hash_size = (path_len_raw >> 6) + 1
|
||||
hop_count = path_len_raw & 0x3F
|
||||
return hop_count, hash_size, hop_count * hash_size
|
||||
|
||||
|
||||
def _to_str(val) -> str:
|
||||
"""Convert bytes or other types to string. Used for expected_ack, pkt_payload, etc."""
|
||||
if val is None:
|
||||
@@ -927,8 +945,9 @@ class DeviceManager:
|
||||
if route_type == 0x00 or route_type == 0x03:
|
||||
pbuf.read(4) # discard transport code
|
||||
|
||||
path_len = pbuf.read(1)[0]
|
||||
path = pbuf.read(path_len).hex()
|
||||
path_len_raw = pbuf.read(1)[0]
|
||||
hop_count, hash_size, path_byte_len = decode_path_len(path_len_raw)
|
||||
path = pbuf.read(path_byte_len).hex()
|
||||
pkt_payload = pbuf.read().hex()
|
||||
|
||||
# Only process GRP_TXT channel message echoes
|
||||
@@ -939,7 +958,7 @@ class DeviceManager:
|
||||
return
|
||||
|
||||
snr = data.get('snr')
|
||||
self._process_echo(pkt_payload, path, snr)
|
||||
self._process_echo(pkt_payload, path, snr, hash_size=hash_size)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling RX_LOG_DATA: {e}")
|
||||
@@ -952,7 +971,8 @@ class DeviceManager:
|
||||
return None
|
||||
return hashlib.sha256(bytes.fromhex(secret_hex)).digest()[0:1].hex()
|
||||
|
||||
def _process_echo(self, pkt_payload: str, path: str, snr: float = None):
|
||||
def _process_echo(self, pkt_payload: str, path: str, snr: float = None,
|
||||
hash_size: int = 1):
|
||||
"""Classify and store an echo: sent echo or incoming echo.
|
||||
|
||||
For sent messages: correlate with pending echo to get pkt_payload.
|
||||
@@ -994,9 +1014,10 @@ class DeviceManager:
|
||||
path=path,
|
||||
snr=snr,
|
||||
direction=direction,
|
||||
hash_size=hash_size,
|
||||
)
|
||||
|
||||
logger.debug(f"Echo ({direction}): path={path} snr={snr} pkt={pkt_payload[:16]}...")
|
||||
logger.debug(f"Echo ({direction}): path={path} snr={snr} hash_size={hash_size} pkt={pkt_payload[:16]}...")
|
||||
|
||||
# Emit SocketIO event for real-time UI update
|
||||
if self.socketio:
|
||||
@@ -1005,6 +1026,7 @@ class DeviceManager:
|
||||
'path': path,
|
||||
'snr': snr,
|
||||
'direction': direction,
|
||||
'hash_size': hash_size,
|
||||
}, namespace='/chat')
|
||||
|
||||
def _is_manual_approval_enabled(self) -> bool:
|
||||
|
||||
@@ -101,6 +101,7 @@ CREATE TABLE IF NOT EXISTS echoes (
|
||||
received_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
direction TEXT DEFAULT 'incoming', -- 'sent' or 'incoming'
|
||||
cm_id INTEGER, -- FK to channel_messages (nullable)
|
||||
hash_size INTEGER NOT NULL DEFAULT 1, -- bytes per hop hash: 1, 2, or 3
|
||||
FOREIGN KEY (cm_id) REFERENCES channel_messages(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user