mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-09 02:03:19 +02:00
Add draft reactions + gifs; region resolution
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Resolve a packet's regional flood-scope (transport code) back to a region name.
|
||||
|
||||
MeshCore's TransportFlood/TransportDirect packets carry a 16-bit "transport code"
|
||||
derived from the region name *and the packet payload* — it is a keyed MAC, not a
|
||||
stable per-region identifier:
|
||||
|
||||
key = SHA256("#" + region_name)[:16] # firmware TransportKey
|
||||
code = HMAC-SHA256(key, payload_type || payload)[:2] # little-endian uint16
|
||||
|
||||
(see ``references/MeshCore/src/helpers/TransportKeyStore.cpp``). Codes ``0x0000``
|
||||
and ``0xFFFF`` are reserved, so the firmware nudges them to ``0x0001`` / ``0xFFFE``.
|
||||
|
||||
Because the code depends on the payload, there is no reverse lookup table: to
|
||||
name a packet's region we recompute the code for each known region name and check
|
||||
for a match. The candidate region names come from the server-side
|
||||
``app_settings.known_regions`` list.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
|
||||
from app.region_scope import normalize_region_scope
|
||||
|
||||
# SHA256("#name")[:16] is deterministic and cheap, but region lists are tiny and
|
||||
# packets are frequent, so cache the derived 16-byte keys by normalized name.
|
||||
_key_cache: dict[str, bytes] = {}
|
||||
|
||||
|
||||
def _region_key(region_name: str) -> bytes | None:
|
||||
"""Return the 16-byte TransportKey for a region name, or None if blank.
|
||||
|
||||
Region names are hashed in their hashtag form (``#name``), matching the
|
||||
firmware (``getAutoKeyFor(id, "#" + name, ...)``).
|
||||
"""
|
||||
normalized = normalize_region_scope(region_name)
|
||||
if not normalized:
|
||||
return None
|
||||
key = _key_cache.get(normalized)
|
||||
if key is None:
|
||||
key = hashlib.sha256(normalized.encode("utf-8")).digest()[:16]
|
||||
_key_cache[normalized] = key
|
||||
return key
|
||||
|
||||
|
||||
def compute_transport_code(region_name: str, payload_type: int, payload: bytes) -> int | None:
|
||||
"""Compute the transport code a region would produce for this payload.
|
||||
|
||||
Returns the little-endian uint16 code (with the firmware's reserved-value
|
||||
adjustment applied), or None if the region name is blank.
|
||||
"""
|
||||
key = _region_key(region_name)
|
||||
if key is None:
|
||||
return None
|
||||
digest = hmac.new(key, bytes([payload_type & 0xFF]) + payload, hashlib.sha256).digest()
|
||||
code = int.from_bytes(digest[:2], "little")
|
||||
if code == 0:
|
||||
code = 1
|
||||
elif code == 0xFFFF:
|
||||
code = 0xFFFE
|
||||
return code
|
||||
|
||||
|
||||
def resolve_region(
|
||||
payload_type: int,
|
||||
payload: bytes,
|
||||
transport_code: int,
|
||||
region_names: list[str],
|
||||
) -> str | None:
|
||||
"""Return the first region name whose code matches ``transport_code``, else None.
|
||||
|
||||
The returned name is the user-facing form as stored in the candidate list
|
||||
(no ``#`` prefix is added or stripped here).
|
||||
"""
|
||||
for name in region_names:
|
||||
if not name:
|
||||
continue
|
||||
if compute_transport_code(name, payload_type, payload) == transport_code:
|
||||
return name
|
||||
return None
|
||||
Reference in New Issue
Block a user