Use more faithful packet frame parsing

This commit is contained in:
Jack Kingsman
2026-03-07 22:35:53 -08:00
parent 48dab293ae
commit 34318e4814
11 changed files with 310 additions and 245 deletions
+94
View File
@@ -9,6 +9,27 @@ The path_len wire byte is packed as [hash_mode:2][hop_count:6]:
Mode 3 (hash_size=4) is reserved and rejected.
"""
from dataclasses import dataclass
MAX_PATH_SIZE = 64
@dataclass(frozen=True)
class ParsedPacketEnvelope:
"""Canonical packet framing parse matching MeshCore Packet::readFrom()."""
header: int
route_type: int
payload_type: int
payload_version: int
path_byte: int
hop_count: int
hash_size: int
path_byte_len: int
path: bytes
payload: bytes
payload_offset: int
def decode_path_byte(path_byte: int) -> tuple[int, int]:
"""Decode a packed path byte into (hop_count, hash_size).
@@ -32,6 +53,79 @@ def path_wire_len(hop_count: int, hash_size: int) -> int:
return hop_count * hash_size
def validate_path_byte(path_byte: int) -> tuple[int, int, int]:
"""Validate a packed path byte using firmware-equivalent rules.
Returns:
(hop_count, hash_size, byte_len)
Raises:
ValueError: If the encoding uses reserved mode 3 or exceeds MAX_PATH_SIZE.
"""
hop_count, hash_size = decode_path_byte(path_byte)
byte_len = path_wire_len(hop_count, hash_size)
if byte_len > MAX_PATH_SIZE:
raise ValueError(
f"Invalid path length {byte_len} bytes exceeds MAX_PATH_SIZE={MAX_PATH_SIZE}"
)
return hop_count, hash_size, byte_len
def parse_packet_envelope(raw_packet: bytes) -> ParsedPacketEnvelope | None:
"""Parse packet framing using firmware Packet::readFrom() semantics.
Validation matches the firmware's path checks:
- reserved mode 3 is invalid
- hop_count * hash_size must not exceed MAX_PATH_SIZE
- at least one payload byte must remain after the path
"""
if len(raw_packet) < 2:
return None
try:
header = raw_packet[0]
route_type = header & 0x03
payload_type = (header >> 2) & 0x0F
payload_version = (header >> 6) & 0x03
offset = 1
if route_type in (0x00, 0x03):
if len(raw_packet) < offset + 4:
return None
offset += 4
if len(raw_packet) < offset + 1:
return None
path_byte = raw_packet[offset]
offset += 1
hop_count, hash_size, path_byte_len = validate_path_byte(path_byte)
if len(raw_packet) < offset + path_byte_len:
return None
path = raw_packet[offset : offset + path_byte_len]
offset += path_byte_len
if offset >= len(raw_packet):
return None
return ParsedPacketEnvelope(
header=header,
route_type=route_type,
payload_type=payload_type,
payload_version=payload_version,
path_byte=path_byte,
hop_count=hop_count,
hash_size=hash_size,
path_byte_len=path_byte_len,
path=path,
payload=raw_packet[offset:],
payload_offset=offset,
)
except (IndexError, ValueError):
return None
def split_path_hex(path_hex: str, hop_count: int) -> list[str]:
"""Split a hex path string into per-hop chunks using the known hop count.