diff --git a/data/mesh_ingestor/providers/meshcore.py b/data/mesh_ingestor/providers/meshcore.py index 3779e55..bab7ba3 100644 --- a/data/mesh_ingestor/providers/meshcore.py +++ b/data/mesh_ingestor/providers/meshcore.py @@ -34,7 +34,9 @@ Connection type is detected automatically from the target string: Node identities are derived from the first four bytes (eight hex characters) of each contact's 32-byte public key, formatted as ``!xxxxxxxx`` to match -the canonical node-ID schema used across the system. +the canonical node-ID schema used across the system. Ingested +``user.shortName`` is the first four hex digits of that key (two bytes), +not the advertised name. """ from __future__ import annotations @@ -118,6 +120,25 @@ def _meshcore_node_id(public_key_hex: str | None) -> str | None: return "!" + public_key_hex[:8].lower() +def _meshcore_short_name(public_key_hex: str | None) -> str: + """Return the first four hex digits of a MeshCore public key as short name. + + Meshtastic-style ``shortName`` fields are four characters wide; MeshCore + ingest uses the leading two bytes of the 32-byte public key in lowercase + hex so the label is stable and unique per key prefix. + + Parameters: + public_key_hex: Full public key as a hex string from the MeshCore API. + + Returns: + Four lowercase hex characters, or an empty string when the key is + missing or shorter than four hex digits. + """ + if not public_key_hex or len(public_key_hex) < 4: + return "" + return public_key_hex[:4].lower() + + def _pubkey_prefix_to_node_id(contacts: dict, pubkey_prefix: str) -> str | None: """Look up a canonical node ID by six-byte public-key prefix. @@ -153,7 +174,7 @@ def _contact_to_node_dict(contact: dict) -> dict: "lastHeard": contact.get("last_advert"), "user": { "longName": name, - "shortName": name[:4] if name else "", + "shortName": _meshcore_short_name(pub_key), "publicKey": pub_key, }, } @@ -180,7 +201,7 @@ def _self_info_to_node_dict(self_info: dict) -> dict: "lastHeard": int(time.time()), "user": { "longName": name, - "shortName": name[:4] if name else "", + "shortName": _meshcore_short_name(pub_key), "publicKey": pub_key, }, } diff --git a/tests/test_provider_unit.py b/tests/test_provider_unit.py index c1be7da..2a31077 100644 --- a/tests/test_provider_unit.py +++ b/tests/test_provider_unit.py @@ -39,6 +39,7 @@ from data.mesh_ingestor.providers.meshcore import ( # noqa: E402 - path setup _make_connection, _make_event_handlers, _meshcore_node_id, + _meshcore_short_name, _process_contact_update, _process_contacts, _process_self_info, @@ -434,6 +435,7 @@ def test_meshcore_node_snapshot_items_with_contacts(monkeypatch): node_id, node_dict = items[0] assert node_id == "!aabbccdd" assert node_dict["user"]["longName"] == "Alice" + assert node_dict["user"]["shortName"] == "aabb" iface.close() @@ -537,6 +539,18 @@ def test_meshcore_node_id_none_on_empty(): assert _meshcore_node_id(None) is None # type: ignore[arg-type] +def test_meshcore_short_name_first_four_hex_digits(): + """_meshcore_short_name returns the first four hex chars, lowercased.""" + assert _meshcore_short_name("AABBccdd" + "00" * 28) == "aabb" + + +def test_meshcore_short_name_empty_when_too_short(): + """_meshcore_short_name returns '' when the key has fewer than four hex digits.""" + assert _meshcore_short_name("") == "" + assert _meshcore_short_name("abc") == "" + assert _meshcore_short_name(None) == "" # type: ignore[arg-type] + + # --------------------------------------------------------------------------- # _pubkey_prefix_to_node_id # --------------------------------------------------------------------------- @@ -576,7 +590,7 @@ def test_contact_to_node_dict_basic_fields(): node = _contact_to_node_dict(contact) assert node["lastHeard"] == 1700000000 assert node["user"]["longName"] == "Alice" - assert node["user"]["shortName"] == "Alic" + assert node["user"]["shortName"] == "aabb" assert node["user"]["publicKey"] == contact["public_key"] @@ -616,7 +630,7 @@ def test_self_info_to_node_dict_basic_fields(): self_info = {"name": "MyNode", "public_key": "bb" * 32} node = _self_info_to_node_dict(self_info) assert node["user"]["longName"] == "MyNode" - assert node["user"]["shortName"] == "MyNo" + assert node["user"]["shortName"] == "bbbb" assert node["user"]["publicKey"] == "bb" * 32 assert isinstance(node["lastHeard"], int) @@ -649,6 +663,7 @@ def test_interface_update_and_snapshot_contacts(): node_id, node_dict = snapshot[0] assert node_id == "!aabbccdd" assert node_dict["user"]["longName"] == "Bob" + assert node_dict["user"]["shortName"] == "aabb" def test_interface_lookup_node_id_by_prefix():