From f73ef29c1e1116556095ecc94b7806909162f985 Mon Sep 17 00:00:00 2001 From: Cobra Date: Tue, 14 Apr 2026 13:39:37 -0400 Subject: [PATCH] net_alerter: Add Strategy 0 MAC deduplication and fix Strategy 3 overwrite bug Strategy 0 now checks if a MAC already exists in any identity's wifi_macs/ble_macs sets before creating a new record. Prevents duplicate identity creation on repeated mDNS/ARP signals. Strategy 3 now conditionally updates existing records instead of unconditionally overwriting them. Preserves signal_count and signal_types accumulation needed for auto-enrollment (2+ signal types). Fixes issue where devices seen only via mDNS/ARP never reached enrollment threshold. --- net_alerter/net_alerter.py | 65 +++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/net_alerter/net_alerter.py b/net_alerter/net_alerter.py index 91489d1..13c9431 100644 --- a/net_alerter/net_alerter.py +++ b/net_alerter/net_alerter.py @@ -186,6 +186,7 @@ def _correlate_or_create_identity(mac: str, signal_type: str, dhcp_fp: str = "", signal_type: "ble", "dhcp", "arp", or "mdns" Lookup strategy: + 0. Check if MAC already exists in any identity's MAC sets 1. If handoff_seq provided, look for matching BLE handoff seq anchor 2. If dhcp_fp provided, look for matching DHCP fingerprint 3. If neither, create new provisional identity @@ -196,6 +197,18 @@ def _correlate_or_create_identity(mac: str, signal_type: str, dhcp_fp: str = "", # Determine signal category: 'ble' or 'wifi' (for dhcp/arp/mdns) signal_category = 'ble' if signal_type == 'ble' else 'wifi' + # Strategy 0: Check if MAC already exists in any identity + for iid, dev in device_store.items(): + if mac in dev['wifi_macs'] or mac in dev['ble_macs']: + # Found existing identity for this MAC — update instead of creating new + dev['last_seen'][mac] = time.time() + dev['signal_types'].add(signal_category) + logging.debug(f"MAC {mac} already linked to identity {iid}, updated {signal_category} signal") + # Check if meets enrollment criteria (2+ signal types) + if len(dev['signal_types']) >= 2 and not dev['enrolled']: + _fire_enrollment_callback(iid) + return iid + # Strategy 1: Match by BLE Handoff sequence (most stable) if handoff_seq is not None: for iid, dev in device_store.items(): @@ -232,23 +245,45 @@ def _correlate_or_create_identity(mac: str, signal_type: str, dhcp_fp: str = "", # Provisional identity based on first MAC seen identity_id = f"prov:{mac}" - dev = _create_identity_record(identity_id) - - # Add initial signal - if signal_type == "ble": - dev['ble_macs'].add(mac) - if handoff_seq is not None: - dev['handoff_seq'] = handoff_seq - dev['signal_types'].add('ble') + # Check if this identity already exists (e.g., multiple mDNS/ARP signals for same MAC) + if identity_id in device_store: + dev = device_store[identity_id] + # Update existing record instead of overwriting + if signal_type == "ble": + dev['ble_macs'].add(mac) + if handoff_seq is not None: + dev['handoff_seq'] = handoff_seq + dev['signal_types'].add('ble') + else: + dev['wifi_macs'].add(mac) + if dhcp_fp: + dev['dhcp_fingerprint'] = dhcp_fp + dev['signal_types'].add('wifi') + dev['last_seen'][mac] = time.time() + # Check if meets enrollment criteria (2+ signal types) + if len(dev['signal_types']) >= 2 and not dev['enrolled']: + _fire_enrollment_callback(identity_id) + logging.debug(f"Updated device identity {identity_id} with {signal_type} signal (MAC: {mac})") else: - dev['wifi_macs'].add(mac) - if dhcp_fp: - dev['dhcp_fingerprint'] = dhcp_fp - dev['signal_types'].add('wifi') + # Create new identity + dev = _create_identity_record(identity_id) + + # Add initial signal + if signal_type == "ble": + dev['ble_macs'].add(mac) + if handoff_seq is not None: + dev['handoff_seq'] = handoff_seq + dev['signal_types'].add('ble') + else: + dev['wifi_macs'].add(mac) + if dhcp_fp: + dev['dhcp_fingerprint'] = dhcp_fp + dev['signal_types'].add('wifi') + + dev['last_seen'][mac] = time.time() + device_store[identity_id] = dev + logging.debug(f"Created new device identity {identity_id} from {signal_type} signal") - dev['last_seen'][mac] = time.time() - device_store[identity_id] = dev - logging.debug(f"Created new device identity {identity_id} from {signal_type} signal") return identity_id