Phase 7: Integration & Migration

This commit is contained in:
Jack Kingsman
2026-03-07 19:48:45 -08:00
parent 8948f2e504
commit 0b91fb18bd
4 changed files with 53 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ import logging
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from app.fanout.base import FanoutModule
from app.path_utils import split_path_hex
logger = logging.getLogger(__name__)
@@ -45,9 +46,12 @@ def _format_body(data: dict, *, include_path: bool) -> str:
if include_path:
paths = data.get("paths")
if paths and isinstance(paths, list) and len(paths) > 0:
path_str = paths[0].get("path", "") if isinstance(paths[0], dict) else ""
first_path = paths[0] if isinstance(paths[0], dict) else {}
path_str = first_path.get("path", "")
path_len = first_path.get("path_len")
else:
path_str = None
path_len = None
if msg_type == "PRIV" and path_str is None:
via = " **via:** [`direct`]"
@@ -56,7 +60,8 @@ def _format_body(data: dict, *, include_path: bool) -> str:
if path_str == "":
via = " **via:** [`direct`]"
else:
hops = [path_str[i : i + 2] for i in range(0, len(path_str), 2)]
hop_count = path_len if isinstance(path_len, int) else len(path_str) // 2
hops = split_path_hex(path_str, hop_count)
if hops:
hop_list = ", ".join(f"`{h}`" for h in hops)
via = f" **via:** [{hop_list}]"

View File

@@ -24,6 +24,7 @@ import aiomqtt
import nacl.bindings
from app.fanout.mqtt_base import BaseMqttPublisher
from app.path_utils import split_path_hex
logger = logging.getLogger(__name__)
@@ -153,23 +154,29 @@ def _calculate_packet_hash(raw_bytes: bytes) -> str:
if has_transport:
offset += 4 # Skip 4 bytes of transport codes
# Read path_len (1 byte on wire). Invalid/truncated packets map to zero hash.
# Read path byte (packed as [hash_mode:2][hop_count:6]).
# Invalid/truncated packets map to zero hash.
if offset >= len(raw_bytes):
return "0" * 16
path_len = raw_bytes[offset]
path_byte = raw_bytes[offset]
offset += 1
hash_mode = (path_byte >> 6) & 0x03
hop_count = path_byte & 0x3F
hash_size = (hash_mode + 1) if hash_mode < 3 else 1
path_wire_len = hop_count * hash_size
# Skip past path to get to payload. Invalid/truncated packets map to zero hash.
if len(raw_bytes) < offset + path_len:
if len(raw_bytes) < offset + path_wire_len:
return "0" * 16
payload_start = offset + path_len
payload_start = offset + path_wire_len
payload_data = raw_bytes[payload_start:]
# Hash: payload_type(1 byte) [+ path_len as uint16_t LE for TRACE] + payload_data
# Hash: payload_type(1 byte) [+ path_byte as uint16_t LE for TRACE] + payload_data
# IMPORTANT: TRACE hash uses the raw wire byte (not decoded hop count) to match firmware.
hash_obj = hashlib.sha256()
hash_obj.update(bytes([payload_type]))
if payload_type == 9: # PAYLOAD_TYPE_TRACE
hash_obj.update(path_len.to_bytes(2, byteorder="little"))
hash_obj.update(path_byte.to_bytes(2, byteorder="little"))
hash_obj.update(payload_data)
return hash_obj.hexdigest()[:16].upper()
@@ -209,20 +216,24 @@ def _decode_packet_fields(raw_bytes: bytes) -> tuple[str, str, str, list[str], i
if len(raw_bytes) <= offset:
return route, packet_type, payload_len, path_values, payload_type
path_len = raw_bytes[offset]
path_byte = raw_bytes[offset]
offset += 1
hash_mode = (path_byte >> 6) & 0x03
hop_count = path_byte & 0x3F
hash_size = (hash_mode + 1) if hash_mode < 3 else 1
path_wire_len = hop_count * hash_size
if len(raw_bytes) < offset + path_len:
if len(raw_bytes) < offset + path_wire_len:
return route, packet_type, payload_len, path_values, payload_type
path_bytes = raw_bytes[offset : offset + path_len]
offset += path_len
path_bytes = raw_bytes[offset : offset + path_wire_len]
offset += path_wire_len
payload_type = (header >> 2) & 0x0F
route = _ROUTE_MAP.get(route_type, "U")
packet_type = str(payload_type)
payload_len = str(max(0, len(raw_bytes) - offset))
path_values = [f"{b:02x}" for b in path_bytes]
path_values = split_path_hex(path_bytes.hex(), hop_count)
return route, packet_type, payload_len, path_values, payload_type
except Exception:

View File

@@ -456,16 +456,20 @@ def _extract_payload_for_hash(raw_packet: bytes) -> bytes | None:
return None
offset += 4
# Get path length
# Get path byte (packed as [hash_mode:2][hop_count:6])
if len(raw_packet) < offset + 1:
return None
path_length = raw_packet[offset]
path_byte = raw_packet[offset]
offset += 1
hash_mode = (path_byte >> 6) & 0x03
hop_count = path_byte & 0x3F
hash_size = (hash_mode + 1) if hash_mode < 3 else 1
path_wire_len = hop_count * hash_size
# Skip path bytes
if len(raw_packet) < offset + path_length:
if len(raw_packet) < offset + path_wire_len:
return None
offset += path_length
offset += path_wire_len
# Rest is payload (may be empty, matching decoder.py behavior)
return raw_packet[offset:]
@@ -638,16 +642,20 @@ def _extract_path_from_packet(raw_packet: bytes) -> str | None:
return None
offset += 4
# Get path length
# Get path byte (packed as [hash_mode:2][hop_count:6])
if len(raw_packet) < offset + 1:
return None
path_length = raw_packet[offset]
path_byte = raw_packet[offset]
offset += 1
hash_mode = (path_byte >> 6) & 0x03
hop_count = path_byte & 0x3F
hash_size = (hash_mode + 1) if hash_mode < 3 else 1
path_wire_len = hop_count * hash_size
# Extract path bytes
if len(raw_packet) < offset + path_length:
if len(raw_packet) < offset + path_wire_len:
return None
path_bytes = raw_packet[offset : offset + path_length]
path_bytes = raw_packet[offset : offset + path_wire_len]
return path_bytes.hex()
except (IndexError, ValueError):

View File

@@ -24,15 +24,18 @@ function extractPayload(packetHex: string): string | null {
offset += 8; // 4 bytes = 8 hex chars
}
// Get path length
// Get path byte (packed as [hash_mode:2][hop_count:6])
if (packetHex.length < offset + 2) return null;
const pathLength = parseInt(packetHex.slice(offset, offset + 2), 16);
const pathByte = parseInt(packetHex.slice(offset, offset + 2), 16);
offset += 2;
const hashMode = (pathByte >> 6) & 0x03;
const hopCount = pathByte & 0x3f;
const hashSize = hashMode < 3 ? hashMode + 1 : 1;
const pathHexChars = hopCount * hashSize * 2;
// Skip path data
const pathBytes = pathLength * 2; // hex chars
if (packetHex.length < offset + pathBytes) return null;
offset += pathBytes;
if (packetHex.length < offset + pathHexChars) return null;
offset += pathHexChars;
// Rest is payload
return packetHex.slice(offset);