mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
185 lines
5.8 KiB
Python
185 lines
5.8 KiB
Python
"""Tests for the centralized path encoding/decoding helpers."""
|
|
|
|
import pytest
|
|
|
|
from app.path_utils import (
|
|
decode_path_byte,
|
|
first_hop_hex,
|
|
infer_hash_size,
|
|
path_wire_len,
|
|
split_path_hex,
|
|
)
|
|
|
|
|
|
class TestDecodePathByte:
|
|
"""Test decoding the packed [hash_mode:2][hop_count:6] byte."""
|
|
|
|
def test_mode0_single_hop(self):
|
|
"""Mode 0 (1-byte hops), 1 hop → path_byte = 0x01."""
|
|
hop_count, hash_size = decode_path_byte(0x01)
|
|
assert hop_count == 1
|
|
assert hash_size == 1
|
|
|
|
def test_mode0_three_hops(self):
|
|
"""Mode 0, 3 hops → path_byte = 0x03."""
|
|
hop_count, hash_size = decode_path_byte(0x03)
|
|
assert hop_count == 3
|
|
assert hash_size == 1
|
|
|
|
def test_mode0_zero_hops(self):
|
|
"""Mode 0, 0 hops (direct) → path_byte = 0x00."""
|
|
hop_count, hash_size = decode_path_byte(0x00)
|
|
assert hop_count == 0
|
|
assert hash_size == 1
|
|
|
|
def test_mode1_two_byte_hops(self):
|
|
"""Mode 1 (2-byte hops), 2 hops → path_byte = 0x42."""
|
|
hop_count, hash_size = decode_path_byte(0x42)
|
|
assert hop_count == 2
|
|
assert hash_size == 2
|
|
|
|
def test_mode1_single_hop(self):
|
|
"""Mode 1 (2-byte hops), 1 hop → path_byte = 0x41."""
|
|
hop_count, hash_size = decode_path_byte(0x41)
|
|
assert hop_count == 1
|
|
assert hash_size == 2
|
|
|
|
def test_mode2_three_byte_hops(self):
|
|
"""Mode 2 (3-byte hops), 1 hop → path_byte = 0x81."""
|
|
hop_count, hash_size = decode_path_byte(0x81)
|
|
assert hop_count == 1
|
|
assert hash_size == 3
|
|
|
|
def test_mode2_max_hops(self):
|
|
"""Mode 2, 63 hops (maximum) → path_byte = 0xBF."""
|
|
hop_count, hash_size = decode_path_byte(0xBF)
|
|
assert hop_count == 63
|
|
assert hash_size == 3
|
|
|
|
def test_mode3_reserved_raises(self):
|
|
"""Mode 3 is reserved and should raise ValueError."""
|
|
with pytest.raises(ValueError, match="Reserved path hash mode 3"):
|
|
decode_path_byte(0xC0)
|
|
|
|
def test_mode3_with_hops_raises(self):
|
|
"""Mode 3 with hop count should also raise."""
|
|
with pytest.raises(ValueError, match="Reserved"):
|
|
decode_path_byte(0xC5)
|
|
|
|
def test_backward_compat_old_firmware(self):
|
|
"""Old firmware packets have upper bits = 0, so mode=0 and path_byte = hop count."""
|
|
for n in range(0, 64):
|
|
hop_count, hash_size = decode_path_byte(n)
|
|
assert hop_count == n
|
|
assert hash_size == 1
|
|
|
|
|
|
class TestPathWireLen:
|
|
def test_basic(self):
|
|
assert path_wire_len(3, 1) == 3
|
|
assert path_wire_len(2, 2) == 4
|
|
assert path_wire_len(1, 3) == 3
|
|
assert path_wire_len(0, 1) == 0
|
|
|
|
|
|
class TestSplitPathHex:
|
|
def test_one_byte_hops(self):
|
|
assert split_path_hex("1a2b3c", 3) == ["1a", "2b", "3c"]
|
|
|
|
def test_two_byte_hops(self):
|
|
assert split_path_hex("1a2b3c4d", 2) == ["1a2b", "3c4d"]
|
|
|
|
def test_three_byte_hops(self):
|
|
assert split_path_hex("1a2b3c4d5e6f", 2) == ["1a2b3c", "4d5e6f"]
|
|
|
|
def test_empty_path(self):
|
|
assert split_path_hex("", 0) == []
|
|
assert split_path_hex("", 3) == []
|
|
|
|
def test_zero_hop_count(self):
|
|
assert split_path_hex("1a2b", 0) == []
|
|
|
|
def test_inconsistent_length_falls_back(self):
|
|
"""If hex length doesn't divide evenly by hop_count, fall back to 2-char chunks."""
|
|
assert split_path_hex("1a2b3c", 2) == ["1a", "2b", "3c"]
|
|
|
|
def test_single_hop_one_byte(self):
|
|
assert split_path_hex("ab", 1) == ["ab"]
|
|
|
|
def test_single_hop_two_bytes(self):
|
|
assert split_path_hex("abcd", 1) == ["abcd"]
|
|
|
|
|
|
class TestFirstHopHex:
|
|
def test_one_byte_hops(self):
|
|
assert first_hop_hex("1a2b3c", 3) == "1a"
|
|
|
|
def test_two_byte_hops(self):
|
|
assert first_hop_hex("1a2b3c4d", 2) == "1a2b"
|
|
|
|
def test_empty(self):
|
|
assert first_hop_hex("", 0) is None
|
|
assert first_hop_hex("", 1) is None
|
|
|
|
def test_direct_path(self):
|
|
assert first_hop_hex("", 0) is None
|
|
|
|
|
|
class TestInferHashSize:
|
|
def test_one_byte(self):
|
|
assert infer_hash_size("1a2b3c", 3) == 1
|
|
|
|
def test_two_byte(self):
|
|
assert infer_hash_size("1a2b3c4d", 2) == 2
|
|
|
|
def test_three_byte(self):
|
|
assert infer_hash_size("1a2b3c4d5e6f", 2) == 3
|
|
|
|
def test_empty_defaults_to_1(self):
|
|
assert infer_hash_size("", 0) == 1
|
|
|
|
def test_inconsistent_defaults_to_1(self):
|
|
assert infer_hash_size("1a2b3", 2) == 1
|
|
|
|
def test_zero_hop_count_defaults_to_1(self):
|
|
assert infer_hash_size("1a2b", 0) == 1
|
|
|
|
|
|
class TestContactToRadioDictHashMode:
|
|
"""Test that Contact.to_radio_dict() correctly derives out_path_hash_mode."""
|
|
|
|
def test_1byte_hops(self):
|
|
from app.models import Contact
|
|
|
|
c = Contact(public_key="aa" * 32, last_path="1a2b3c", last_path_len=3)
|
|
d = c.to_radio_dict()
|
|
assert d["out_path_hash_mode"] == 0 # infer_hash_size=1, mode=0
|
|
|
|
def test_2byte_hops(self):
|
|
from app.models import Contact
|
|
|
|
c = Contact(public_key="bb" * 32, last_path="1a2b3c4d", last_path_len=2)
|
|
d = c.to_radio_dict()
|
|
assert d["out_path_hash_mode"] == 1 # infer_hash_size=2, mode=1
|
|
|
|
def test_3byte_hops(self):
|
|
from app.models import Contact
|
|
|
|
c = Contact(public_key="cc" * 32, last_path="1a2b3c4d5e6f", last_path_len=2)
|
|
d = c.to_radio_dict()
|
|
assert d["out_path_hash_mode"] == 2 # infer_hash_size=3, mode=2
|
|
|
|
def test_no_path_defaults_to_mode0(self):
|
|
from app.models import Contact
|
|
|
|
c = Contact(public_key="dd" * 32, last_path=None, last_path_len=-1)
|
|
d = c.to_radio_dict()
|
|
assert d["out_path_hash_mode"] == 0
|
|
|
|
def test_empty_path_defaults_to_mode0(self):
|
|
from app.models import Contact
|
|
|
|
c = Contact(public_key="ee" * 32, last_path="", last_path_len=0)
|
|
d = c.to_radio_dict()
|
|
assert d["out_path_hash_mode"] == 0
|