mirror of
https://github.com/smittix/intercept.git
synced 2026-04-25 07:10:00 -07:00
chore: Bump version to v2.18.0
Bluetooth enhancements (service data inspector, appearance codes, MAC cluster tracking, behavioral flags, IRK badges, distance estimation), ACARS SoapySDR multi-backend support, dump1090 stale process cleanup, GPS error state, and proximity radar/signal card UI improvements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,9 @@ from .constants import (
|
||||
)
|
||||
from .dbus_scanner import DBusScanner
|
||||
from .fallback_scanner import FallbackScanner
|
||||
from .ubertooth_scanner import UbertoothScanner
|
||||
from .heuristics import HeuristicsEngine
|
||||
from .irk_extractor import get_paired_irks
|
||||
from .models import BTDeviceAggregate, BTObservation, ScanStatus, SystemCapabilities
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -57,6 +59,7 @@ class BluetoothScanner:
|
||||
# Scanner backends
|
||||
self._dbus_scanner: Optional[DBusScanner] = None
|
||||
self._fallback_scanner: Optional[FallbackScanner] = None
|
||||
self._ubertooth_scanner: Optional[UbertoothScanner] = None
|
||||
self._active_backend: Optional[str] = None
|
||||
|
||||
# Event queue for SSE streaming
|
||||
@@ -113,6 +116,8 @@ class BluetoothScanner:
|
||||
|
||||
if mode == 'dbus':
|
||||
started, backend_used = self._start_dbus(adapter, transport, rssi_threshold)
|
||||
elif mode == 'ubertooth':
|
||||
started, backend_used = self._start_ubertooth()
|
||||
|
||||
# Fallback: try non-DBus methods if DBus failed or wasn't requested
|
||||
if not started and (original_mode == 'auto' or mode in ('bleak', 'hcitool', 'bluetoothctl')):
|
||||
@@ -168,6 +173,18 @@ class BluetoothScanner:
|
||||
logger.warning(f"DBus scanner failed: {e}")
|
||||
return False, None
|
||||
|
||||
def _start_ubertooth(self) -> tuple[bool, Optional[str]]:
|
||||
"""Start Ubertooth One scanner."""
|
||||
try:
|
||||
self._ubertooth_scanner = UbertoothScanner(
|
||||
on_observation=self._handle_observation,
|
||||
)
|
||||
if self._ubertooth_scanner.start():
|
||||
return True, 'ubertooth'
|
||||
except Exception as e:
|
||||
logger.warning(f"Ubertooth scanner failed: {e}")
|
||||
return False, None
|
||||
|
||||
def _start_fallback(self, adapter: str, preferred: str) -> tuple[bool, Optional[str]]:
|
||||
"""Start fallback scanner."""
|
||||
try:
|
||||
@@ -204,6 +221,10 @@ class BluetoothScanner:
|
||||
self._fallback_scanner.stop()
|
||||
self._fallback_scanner = None
|
||||
|
||||
if self._ubertooth_scanner:
|
||||
self._ubertooth_scanner.stop()
|
||||
self._ubertooth_scanner = None
|
||||
|
||||
# Update status
|
||||
self._status.is_scanning = False
|
||||
self._active_backend = None
|
||||
@@ -216,6 +237,47 @@ class BluetoothScanner:
|
||||
|
||||
logger.info("Bluetooth scan stopped")
|
||||
|
||||
def _match_irk(self, device: BTDeviceAggregate) -> None:
|
||||
"""Check if a device address resolves against any paired IRK."""
|
||||
if device.irk_hex is not None:
|
||||
return # Already matched
|
||||
|
||||
address = device.address
|
||||
if not address or len(address.replace(':', '').replace('-', '')) not in (12, 32):
|
||||
return
|
||||
|
||||
# Only attempt RPA resolution on 6-byte addresses
|
||||
addr_clean = address.replace(':', '').replace('-', '')
|
||||
if len(addr_clean) != 12:
|
||||
return
|
||||
|
||||
try:
|
||||
paired = get_paired_irks()
|
||||
except Exception:
|
||||
return
|
||||
|
||||
if not paired:
|
||||
return
|
||||
|
||||
try:
|
||||
from utils.bt_locate import resolve_rpa
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
for entry in paired:
|
||||
irk_hex = entry.get('irk_hex', '')
|
||||
if not irk_hex or len(irk_hex) != 32:
|
||||
continue
|
||||
try:
|
||||
irk = bytes.fromhex(irk_hex)
|
||||
if resolve_rpa(irk, address):
|
||||
device.irk_hex = irk_hex
|
||||
device.irk_source_name = entry.get('name')
|
||||
logger.debug(f"IRK match for {address}: {entry.get('name', 'unnamed')}")
|
||||
return
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def _handle_observation(self, observation: BTObservation) -> None:
|
||||
"""Handle incoming observation from scanner backend."""
|
||||
try:
|
||||
@@ -225,15 +287,27 @@ class BluetoothScanner:
|
||||
# Evaluate heuristics
|
||||
self._heuristics.evaluate(device)
|
||||
|
||||
# Check for IRK match
|
||||
self._match_irk(device)
|
||||
|
||||
# Update device count
|
||||
with self._lock:
|
||||
self._status.devices_found = self._aggregator.device_count
|
||||
|
||||
# Build summary with MAC cluster count
|
||||
summary = device.to_summary_dict()
|
||||
if device.payload_fingerprint_id:
|
||||
summary['mac_cluster_count'] = self._aggregator.get_fingerprint_mac_count(
|
||||
device.payload_fingerprint_id
|
||||
)
|
||||
else:
|
||||
summary['mac_cluster_count'] = 0
|
||||
|
||||
# Queue event
|
||||
self._queue_event({
|
||||
'type': 'device',
|
||||
'action': 'update',
|
||||
'device': device.to_summary_dict(),
|
||||
'device': summary,
|
||||
})
|
||||
|
||||
# Callbacks
|
||||
@@ -398,6 +472,7 @@ class BluetoothScanner:
|
||||
backend_alive = (
|
||||
(self._dbus_scanner and self._dbus_scanner.is_scanning)
|
||||
or (self._fallback_scanner and self._fallback_scanner.is_scanning)
|
||||
or (self._ubertooth_scanner and self._ubertooth_scanner.is_scanning)
|
||||
)
|
||||
if not backend_alive:
|
||||
self._status.is_scanning = False
|
||||
|
||||
Reference in New Issue
Block a user