Skip to content
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions meshtastic/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,24 @@

logger = logging.getLogger(__name__)

def xor_hash(data: bytes) -> int:
"""Compute an XOR hash from bytes."""
result = 0
for char in data:
result ^= char
return result

def generate_hash(name: str, key: str) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good, but a few thoughts:

  1. this function, xor_hash, and a variable for the default key (as bytes, I think, rather than the base64 version) really all belong in meshtastic.util rather than here.
  2. There's multiple forms of hashing in firmware so this should be named to denote that, perhaps channel_hash. If we later want to add the frequency-slot-style hash, better if it's distinguished better from the start.
  3. we should be more permissive with the function signature; both arguments should accept Union[str, bytes] and in the function body we can use isinstance to determine if they need translation via something like:
if isinstance(name, str):
    name = bytes(name, "utf-8")
if isinstance(key, str):
    key = base64.b64decode(key.replace("-", "+").replace("_", "/").encode("utf-8"))
if len(key) == 1:
    key = default_key[:-1] + key

Hopefully these changes sound good to you. If possible I'd also love to see some tests for these functions added but I know I've already made several requests :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some changes in as requested, looking at tests now something like ? (I didnt fully compute)

def test_channel_hash():
    """Test channel_hash"""
    assert channel_hash(b"123") == 48
    assert channel_hash(b"") == 0

def test_generate_channel_hash(data: bytes) -> int:
    """test a channel hash with testname: LongFast and test PSK: AQ=="""
    assert generate_channel_hash(b"LongFast", b"\x01") == 108

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sort of thing, though it could probably be more in-depth than that, ideally even using the hypothesis library to do some property-based testing (but that's a bit complex to explain in a GitHub comment). I've got a little bit of time here, so I'll try to add some of these things while doing things already here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ad04c26 threw in a bunch here if you're curious what I was thinking of. Should be good to merge in a sec though, as long as CI doesn't complain again for some reason.

"""generate the channel number by hashing the channel name and psk"""
if key == "AQ==":
key = "1PG7OiApB1nwvP+rz05pAQ=="
replaced_key = key.replace("-", "+").replace("_", "/")
key_bytes = base64.b64decode(replaced_key.encode("utf-8"))
h_name = xor_hash(bytes(name, "utf-8"))
h_key = xor_hash(key_bytes)
result: int = h_name ^ h_key
return result

class Node:
"""A model of a (local or remote) node in the mesh

Expand Down Expand Up @@ -1043,3 +1061,20 @@ def ensureSessionKey(self):
nodeid = int(self.nodeNum[1:],16)
if self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") is None:
self.requestConfig(admin_pb2.AdminMessage.SESSIONKEY_CONFIG)

def get_channels_with_hash(self):
"""Return a list of dicts with channel info and hash."""
result = []
if self.channels:
for c in self.channels:
if c.settings and hasattr(c.settings, "name") and hasattr(c.settings, "psk"):
hash_val = generate_hash(c.settings.name, c.settings.psk)
else:
hash_val = None
result.append({
"index": c.index,
"role": channel_pb2.Channel.Role.Name(c.role),
"name": c.settings.name if c.settings and hasattr(c.settings, "name") else "",
"hash": hash_val,
})
return result
Loading