Use actual pubkey matching for path update, not default, and don't action the serial path update events

This commit is contained in:
Jack Kingsman
2026-02-16 19:06:09 -08:00
parent 72f12d80e5
commit 8ca48cd6bc
2 changed files with 95 additions and 20 deletions

View File

@@ -189,15 +189,41 @@ async def on_rx_log_data(event: "Event") -> None:
async def on_path_update(event: "Event") -> None:
"""Handle path update events."""
payload = event.payload
logger.debug("Path update for %s", payload.get("pubkey_prefix"))
public_key = str(payload.get("public_key", "")).lower()
pubkey_prefix = str(payload.get("pubkey_prefix", "")).lower()
pubkey_prefix = payload.get("pubkey_prefix", "")
path = payload.get("path", "")
path_len = payload.get("path_len", -1)
contact: Contact | None = None
if public_key:
logger.debug("Path update for %s", public_key[:12])
contact = await ContactRepository.get_by_key(public_key)
elif pubkey_prefix:
# Legacy compatibility: older payloads may only include a prefix.
logger.debug("Path update for prefix %s", pubkey_prefix)
contact = await ContactRepository.get_by_key_prefix(pubkey_prefix)
else:
logger.debug("PATH_UPDATE missing public_key/pubkey_prefix, skipping")
return
existing = await ContactRepository.get_by_key_prefix(pubkey_prefix)
if existing:
await ContactRepository.update_path(existing.public_key, path, path_len)
if not contact:
return
# PATH_UPDATE is a serial control push event from firmware (not an RF packet).
# Current meshcore payloads only include public_key for this event.
# RF route/path bytes are handled via RX_LOG_DATA -> process_raw_packet,
# so if path fields are absent here we treat this as informational only.
path = payload.get("path")
path_len = payload.get("path_len")
if path is None or path_len is None:
logger.debug("PATH_UPDATE for %s has no path payload, skipping DB update", contact.public_key[:12])
return
try:
normalized_path_len = int(path_len)
except (TypeError, ValueError):
logger.warning("Invalid path_len in PATH_UPDATE for %s: %r", contact.public_key[:12], path_len)
return
await ContactRepository.update_path(contact.public_key, str(path), normalized_path_len)
async def on_new_contact(event: "Event") -> None:

View File

@@ -455,7 +455,7 @@ class TestOnPathUpdate:
@pytest.mark.asyncio
async def test_updates_path_for_existing_contact(self, test_db):
"""Path is updated when the contact exists in the database."""
"""Path is updated when the contact exists and payload includes full key."""
from app.event_handlers import on_path_update
await ContactRepository.upsert(
@@ -469,7 +469,7 @@ class TestOnPathUpdate:
class MockEvent:
payload = {
"pubkey_prefix": "aaaaaa",
"public_key": "aa" * 32,
"path": "0102",
"path_len": 2,
}
@@ -489,7 +489,7 @@ class TestOnPathUpdate:
class MockEvent:
payload = {
"pubkey_prefix": "unknown",
"public_key": "cc" * 32,
"path": "0102",
"path_len": 2,
}
@@ -498,8 +498,8 @@ class TestOnPathUpdate:
await on_path_update(MockEvent())
@pytest.mark.asyncio
async def test_uses_defaults_for_missing_payload_fields(self, test_db):
"""Missing payload fields fall back to defaults (empty path, -1 length)."""
async def test_legacy_prefix_payload_still_supported(self, test_db):
"""Legacy prefix payloads still update path when uniquely resolvable."""
from app.event_handlers import on_path_update
await ContactRepository.upsert(
@@ -511,20 +511,69 @@ class TestOnPathUpdate:
}
)
class MockEvent:
payload = {
"pubkey_prefix": "bbbbbb",
"path": "0a0b",
"path_len": 2,
}
await on_path_update(MockEvent())
contact = await ContactRepository.get_by_key("bb" * 32)
assert contact is not None
assert contact.last_path == "0a0b"
assert contact.last_path_len == 2
@pytest.mark.asyncio
async def test_missing_path_fields_does_not_modify_contact(self, test_db):
"""Current PATH_UPDATE payloads without path fields should not mutate DB path."""
from app.event_handlers import on_path_update
await ContactRepository.upsert(
{
"public_key": "dd" * 32,
"name": "Dana",
"type": 1,
"flags": 0,
}
)
await ContactRepository.update_path("dd" * 32, "beef", 2)
class MockEvent:
payload = {"public_key": "dd" * 32}
await on_path_update(MockEvent())
contact = await ContactRepository.get_by_key("dd" * 32)
assert contact is not None
assert contact.last_path == "beef"
assert contact.last_path_len == 2
@pytest.mark.asyncio
async def test_missing_identity_fields_noop(self, test_db):
"""PATH_UPDATE with no key fields should be a no-op."""
from app.event_handlers import on_path_update
await ContactRepository.upsert(
{
"public_key": "ee" * 32,
"name": "Eve",
"type": 1,
"flags": 0,
}
)
await ContactRepository.update_path("ee" * 32, "abcd", 2)
class MockEvent:
payload = {}
await on_path_update(MockEvent())
# With empty prefix, get_by_key_prefix("") should return None since
# no key starts with "" uniquely (if multiple contacts exist) or
# the single contact if only one. But with prefix="", the LIKE query
# matches all contacts. With exactly one contact, it returns it.
# The update_path call sets path="" and path_len=-1.
contact = await ContactRepository.get_by_key("bb" * 32)
contact = await ContactRepository.get_by_key("ee" * 32)
assert contact is not None
assert contact.last_path == ""
assert contact.last_path_len == -1
assert contact.last_path == "abcd"
assert contact.last_path_len == 2
class TestOnNewContact: