Files
meshcore-bot/tests/unit/test_scope_matching.py
T
Adam Gessaman 4f820ffbd4 Enhance flood scope handling and channel fetching logic
- Updated `config.ini.example` to clarify flood scope configuration, introducing the auto-hashtag format for region names and adding support for multi-scope replies.
- Refactored `ChannelManager` to improve handling of empty channels, adjusting timeout logic and increasing request delay to prevent overwhelming devices.
- Enhanced `CommandManager` to load flood scope keys for HMAC matching and normalize scope names for consistency.
- Implemented scope matching in `MessageHandler` to ensure replies respect configured flood scopes, improving message routing accuracy.
- Updated `MeshMessage` model to include a `reply_scope` attribute for tracking matched flood scopes.
2026-04-08 21:14:04 -07:00

88 lines
3.0 KiB
Python

"""Unit tests for TC_FLOOD scope matching via HMAC-SHA256.
_match_scope mirrors the firmware's TransportKey::calcTransportCode:
HMAC-SHA256(scope_key, payload_type_byte || pkt_payload)[0:2] as uint16_le
make_transport_code is the same computation used to generate known-good test inputs.
"""
import hmac as hmac_mod
from hashlib import sha256
from modules.message_handler import MessageHandler
def _scope_key(scope_name: str) -> bytes:
"""16-byte scope key for a named region (auto-hashtag convention)."""
return sha256(scope_name.encode()).digest()[:16]
def make_transport_code(scope_name: str, payload_type: int, pkt_payload: bytes) -> int:
"""Mirror the firmware's calcTransportCode — used to build test inputs."""
key = _scope_key(scope_name)
data = bytes([payload_type]) + pkt_payload
digest = hmac_mod.new(key, data, sha256).digest()
code = int.from_bytes(digest[:2], "little")
if code == 0:
code = 1
elif code == 0xFFFF:
code = 0xFFFE
return code
PAYLOAD_TYPE = 0x05 # GRP_TXT (channel message)
PAYLOAD = b"\xde\xad\xbe\xef\x01\x02\x03\x04" # arbitrary encrypted bytes
def test_matching_scope_returned():
key = _scope_key("#west")
scope_keys = {"#west": key}
tc = make_transport_code("#west", PAYLOAD_TYPE, PAYLOAD)
result = MessageHandler._match_scope(tc, PAYLOAD_TYPE, PAYLOAD, scope_keys)
assert result == "#west"
def test_non_matching_scope_returns_none():
key_east = _scope_key("#east")
scope_keys = {"#east": key_east}
tc = make_transport_code("#west", PAYLOAD_TYPE, PAYLOAD)
result = MessageHandler._match_scope(tc, PAYLOAD_TYPE, PAYLOAD, scope_keys)
assert result is None
def test_empty_scope_map_returns_none():
tc = make_transport_code("#west", PAYLOAD_TYPE, PAYLOAD)
assert MessageHandler._match_scope(tc, PAYLOAD_TYPE, PAYLOAD, {}) is None
def test_none_transport_code_returns_none():
key = _scope_key("#west")
scope_keys = {"#west": key}
assert MessageHandler._match_scope(None, PAYLOAD_TYPE, PAYLOAD, scope_keys) is None
def test_correct_scope_selected_from_multiple():
key_west = _scope_key("#west")
key_east = _scope_key("#east")
scope_keys = {"#west": key_west, "#east": key_east}
tc = make_transport_code("#east", PAYLOAD_TYPE, PAYLOAD)
result = MessageHandler._match_scope(tc, PAYLOAD_TYPE, PAYLOAD, scope_keys)
assert result == "#east"
def test_wrong_payload_does_not_match():
key = _scope_key("#west")
scope_keys = {"#west": key}
tc = make_transport_code("#west", PAYLOAD_TYPE, PAYLOAD)
different_payload = b"\x00\x00\x00\x00"
result = MessageHandler._match_scope(tc, PAYLOAD_TYPE, different_payload, scope_keys)
assert result is None
def test_different_payload_type_does_not_match():
key = _scope_key("#west")
scope_keys = {"#west": key}
tc = make_transport_code("#west", PAYLOAD_TYPE, PAYLOAD)
result = MessageHandler._match_scope(tc, PAYLOAD_TYPE + 1, PAYLOAD, scope_keys)
assert result is None