Skip to content

Commit ad04c26

Browse files
committed
split out constant, improve logic some, add tests for channel_hash and generate_channel_hash
1 parent 0e67ef3 commit ad04c26

File tree

2 files changed

+57
-13
lines changed

2 files changed

+57
-13
lines changed

meshtastic/tests/test_util.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@
1111
from meshtastic.supported_device import SupportedDevice
1212
from meshtastic.protobuf import mesh_pb2
1313
from meshtastic.util import (
14+
DEFAULT_KEY,
1415
Timeout,
1516
active_ports_on_supported_devices,
1617
camel_to_snake,
1718
catchAndIgnore,
19+
channel_hash,
1820
convert_mac_addr,
1921
eliminate_duplicate_port,
2022
findPorts,
2123
fixme,
2224
fromPSK,
2325
fromStr,
26+
generate_channel_hash,
2427
genPSK256,
2528
hexstr,
2629
ipstr,
@@ -670,3 +673,45 @@ def test_shorthex():
670673
assert result == b'\x05'
671674
result = fromStr('0xffff')
672675
assert result == b'\xff\xff'
676+
677+
def test_channel_hash_basics():
678+
"Test the default key and LongFast with channel_hash"
679+
assert channel_hash(DEFAULT_KEY) == 2
680+
assert channel_hash("LongFast".encode("utf-8")) == 10
681+
682+
@given(st.text(min_size=1, max_size=12))
683+
def test_channel_hash_fuzz(channel_name):
684+
"Test channel_hash with fuzzed channel names, ensuring it produces single-byte values"
685+
hashed = channel_hash(channel_name.encode("utf-8"))
686+
assert 0 <= hashed <= 0xFF
687+
688+
def test_generate_channel_hash_basics():
689+
"Test the default key and LongFast/MediumFast with generate_channel_hash"
690+
assert generate_channel_hash("LongFast", "AQ==") == 8
691+
assert generate_channel_hash("LongFast", bytes([1])) == 8
692+
assert generate_channel_hash("LongFast", DEFAULT_KEY) == 8
693+
assert generate_channel_hash("MediumFast", DEFAULT_KEY) == 31
694+
695+
@given(st.text(min_size=1, max_size=12))
696+
def test_generate_channel_hash_fuzz_default_key(channel_name):
697+
"Test generate_channel_hash with fuzzed channel names and the default key, ensuring it produces single-byte values"
698+
hashed = generate_channel_hash(channel_name, DEFAULT_KEY)
699+
assert 0 <= hashed <= 0xFF
700+
701+
@given(st.text(min_size=1, max_size=12), st.binary(min_size=1, max_size=1))
702+
def test_generate_channel_hash_fuzz_simple(channel_name, key_bytes):
703+
"Test generate_channel_hash with fuzzed channel names and one-byte keys, ensuring it produces single-byte values"
704+
hashed = generate_channel_hash(channel_name, key_bytes)
705+
assert 0 <= hashed <= 0xFF
706+
707+
@given(st.text(min_size=1, max_size=12), st.binary(min_size=16, max_size=16))
708+
def test_generate_channel_hash_fuzz_aes128(channel_name, key_bytes):
709+
"Test generate_channel_hash with fuzzed channel names and 128-bit keys, ensuring it produces single-byte values"
710+
hashed = generate_channel_hash(channel_name, key_bytes)
711+
assert 0 <= hashed <= 0xFF
712+
713+
@given(st.text(min_size=1, max_size=12), st.binary(min_size=32, max_size=32))
714+
def test_generate_channel_hash_fuzz_aes256(channel_name, key_bytes):
715+
"Test generate_channel_hash with fuzzed channel names and 256-bit keys, ensuring it produces single-byte values"
716+
hashed = generate_channel_hash(channel_name, key_bytes)
717+
assert 0 <= hashed <= 0xFF

meshtastic/util.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040

4141
logger = logging.getLogger(__name__)
4242

43+
DEFAULT_KEY = base64.b64decode("1PG7OiApB1nwvP+rz05pAQ==".encode("utf-8"))
44+
4345
def quoteBooleans(a_string: str) -> str:
4446
"""Quote booleans
4547
given a string that contains ": true", replace with ": 'true'" (or false)
@@ -372,24 +374,21 @@ def channel_hash(data: bytes) -> int:
372374
result ^= char
373375
return result
374376

375-
def generate_channel_hash(name, key) -> int:
377+
def generate_channel_hash(name: Union[str, bytes], key: Union[str, bytes]) -> int:
376378
"""generate the channel number by hashing the channel name and psk (accepts str or bytes for both)"""
377379
# Handle key as str or bytes
378-
if isinstance(key, bytes):
379-
key = key.decode("utf-8")
380-
if key == "AQ==":
381-
key = "1PG7OiApB1nwvP+rz05pAQ=="
382-
replaced_key = key.replace("-", "+").replace("_", "/")
383-
key_bytes = base64.b64decode(replaced_key.encode("utf-8"))
380+
if isinstance(key, str):
381+
key = base64.b64decode(key.replace("-", "+").replace("_", "/").encode("utf-8"))
382+
383+
if len(key) == 1:
384+
key = DEFAULT_KEY[:-1] + key
384385

385386
# Handle name as str or bytes
386-
if isinstance(name, bytes):
387-
name_bytes = name
388-
else:
389-
name_bytes = name.encode("utf-8")
387+
if isinstance(name, str):
388+
name = name.encode("utf-8")
390389

391-
h_name = channel_hash(name_bytes)
392-
h_key = channel_hash(key_bytes)
390+
h_name = channel_hash(name)
391+
h_key = channel_hash(key)
393392
result: int = h_name ^ h_key
394393
return result
395394

0 commit comments

Comments
 (0)