Refactor advert frame handling in CompanionFrameServer

- Improved the `_build_advert_push_frames` function to handle optional fields and ensure thread safety.
- Enhanced the `on_advert_received` method to robustly process incoming contact data, including better handling of public keys and optional fields for advert details.
- Added error handling to log exceptions during advert processing, improving reliability.
This commit is contained in:
agessaman
2026-02-15 19:33:04 -08:00
parent c07d24d387
commit a3f96962ff
+74 -58
View File
@@ -94,20 +94,28 @@ logger = logging.getLogger("CompanionFrameServer")
def _build_advert_push_frames(data: dict) -> tuple[bytes, Optional[bytes]]:
"""Build PUSH_CODE_ADVERT short frame and optional PUSH_CODE_NEW_ADVERT full frame from extracted data. Thread-safe for asyncio.to_thread."""
pubkey_b = data["pubkey_b"]
pubkey_b = data.get("pubkey_b", b"")
if isinstance(pubkey_b, bytes):
pubkey_b = pubkey_b[:32].ljust(32, b"\x00")
else:
pubkey_b = b"\x00" * 32
short = bytes([PUSH_CODE_ADVERT]) + pubkey_b
if not data.get("include_full"):
return (short, None)
op = data.get("out_path", b"")
op = (op if isinstance(op, bytes) else bytes(op or []))[:MAX_PATH_SIZE].ljust(MAX_PATH_SIZE, b"\x00")
nb = data.get("name_b", b"")
nb = (nb if isinstance(nb, bytes) else (nb.encode("utf-8", errors="replace") if isinstance(nb, str) else b""))[:32].ljust(32, b"\x00")
full = (
bytes([PUSH_CODE_NEW_ADVERT])
+ pubkey_b
+ bytes([data["adv_type"], data["flags"], data["opl_byte"]])
+ data["out_path"]
+ data["name_b"]
+ struct.pack("<I", data["last_advert"])
+ struct.pack("<i", data["gps_lat_int"])
+ struct.pack("<i", data["gps_lon_int"])
+ struct.pack("<I", data["lastmod"])
+ bytes([data.get("adv_type", 0), data.get("flags", 0), data.get("opl_byte", 0xFF)])
+ op
+ nb
+ struct.pack("<I", data.get("last_advert", 0))
+ struct.pack("<i", data.get("gps_lat_int", 0))
+ struct.pack("<i", data.get("gps_lon_int", 0))
+ struct.pack("<I", data.get("lastmod", 0))
)
return (short, full)
@@ -209,58 +217,66 @@ class CompanionFrameServer:
_write_push(data)
async def on_advert_received(contact):
if isinstance(contact, dict):
pubkey = contact.get("public_key", b"")
try:
if isinstance(contact, dict):
pubkey = contact.get("public_key", b"")
if isinstance(pubkey, str):
pubkey = bytes.fromhex(pubkey)
else:
pubkey = getattr(contact, "public_key", getattr(contact, "pub_key", b""))
if isinstance(pubkey, str):
pubkey = bytes.fromhex(pubkey)
else:
pubkey = getattr(contact, "public_key", getattr(contact, "pub_key", b""))
if isinstance(pubkey, str):
pubkey = bytes.fromhex(pubkey)
if len(pubkey) < 32:
return
pubkey_b = pubkey[:32] if isinstance(pubkey, bytes) else bytes.fromhex(str(pubkey))[:32]
include_full = (
not isinstance(contact, dict)
and hasattr(contact, "name")
and contact.name
)
data = {
"pubkey_b": pubkey_b,
"include_full": include_full,
"adv_type": 0,
"flags": 0,
"opl_byte": 0xFF,
"out_path": b"\x00" * MAX_PATH_SIZE,
"name_b": b"\x00" * 32,
"last_advert": 0,
"gps_lat_int": 0,
"gps_lon_int": 0,
"lastmod": 0,
}
if include_full:
data["adv_type"] = getattr(contact, "adv_type", 0)
data["flags"] = getattr(contact, "flags", 0)
opl = getattr(contact, "out_path_len", -1)
data["opl_byte"] = 0xFF if opl < 0 else min(opl, 255)
out_path = getattr(contact, "out_path", b"") or b""
if isinstance(out_path, str):
out_path = bytes.fromhex(out_path) if out_path else b""
elif isinstance(out_path, (list, bytearray)):
out_path = bytes(out_path)
data["out_path"] = bytes(out_path)[:MAX_PATH_SIZE].ljust(MAX_PATH_SIZE, b"\x00")
name = contact.name
data["name_b"] = (name.encode("utf-8")[:32] if isinstance(name, str) else name[:32]).ljust(32, b"\x00")
data["last_advert"] = getattr(contact, "last_advert_timestamp", 0)
data["lastmod"] = getattr(contact, "lastmod", 0)
gps_lat = getattr(contact, "gps_lat", 0.0)
gps_lon = getattr(contact, "gps_lon", 0.0)
data["gps_lat_int"] = int(gps_lat * 1e6)
data["gps_lon_int"] = int(gps_lon * 1e6)
short, full = await asyncio.to_thread(_build_advert_push_frames, data)
_write_push(short)
if full is not None:
_write_push(full)
if not isinstance(pubkey, bytes) or len(pubkey) < 32:
return
pubkey_b = pubkey[:32].ljust(32, b"\x00")
include_full = (
not isinstance(contact, dict)
and getattr(contact, "name", None)
)
data = {
"pubkey_b": pubkey_b,
"include_full": bool(include_full),
"adv_type": 0,
"flags": 0,
"opl_byte": 0xFF,
"out_path": b"\x00" * MAX_PATH_SIZE,
"name_b": b"\x00" * 32,
"last_advert": 0,
"gps_lat_int": 0,
"gps_lon_int": 0,
"lastmod": 0,
}
if include_full:
data["adv_type"] = getattr(contact, "adv_type", 0)
data["flags"] = getattr(contact, "flags", 0)
opl = getattr(contact, "out_path_len", -1)
data["opl_byte"] = 0xFF if opl < 0 else min(opl, 255)
out_path = getattr(contact, "out_path", b"") or b""
if isinstance(out_path, str):
out_path = bytes.fromhex(out_path) if out_path else b""
elif isinstance(out_path, (list, bytearray)):
out_path = bytes(out_path)
data["out_path"] = bytes(out_path)[:MAX_PATH_SIZE].ljust(MAX_PATH_SIZE, b"\x00")
name = getattr(contact, "name", "") or ""
if isinstance(name, str):
data["name_b"] = name.encode("utf-8", errors="replace")[:32].ljust(32, b"\x00")
elif isinstance(name, bytes):
data["name_b"] = name[:32].ljust(32, b"\x00")
else:
data["name_b"] = b"\x00" * 32
data["last_advert"] = getattr(contact, "last_advert_timestamp", 0)
data["lastmod"] = getattr(contact, "lastmod", 0)
gps_lat = getattr(contact, "gps_lat", 0.0)
gps_lon = getattr(contact, "gps_lon", 0.0)
data["gps_lat_int"] = int(gps_lat * 1e6)
data["gps_lon_int"] = int(gps_lon * 1e6)
short, full = await asyncio.to_thread(_build_advert_push_frames, data)
_write_push(short)
if full is not None:
await asyncio.sleep(0)
_write_push(full)
except Exception as e:
logger.exception("advert_received callback error: %s", e)
async def on_contact_path_updated(pub_key, path_len, path):
if isinstance(pub_key, bytes) and len(pub_key) >= 32: