Fix Bluetooth bytes conversion and WiFi monitor mode detection

- Fix "cannot convert 'str' object to bytes" error in BLE identity engine
  by adding robust _convert_to_bytes() helper that handles bytes, hex
  strings, bytearrays, and arrays
- Improve DBus scanner to safely handle various data types for
  manufacturer_data and service_data with proper error handling
- Add monitor mode interface detection in WiFi scanner to provide clear
  error message when quick scan is attempted on monitor mode interface

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-21 23:25:22 +00:00
parent 54a47b03c2
commit f01502ff32
3 changed files with 90 additions and 6 deletions

View File

@@ -305,8 +305,18 @@ class DBusScanner:
if mfr_data:
for mid, mdata in mfr_data.items():
manufacturer_id = int(mid)
if isinstance(mdata, dbus.Array):
manufacturer_data = bytes(mdata)
# Handle various DBus data types safely
try:
if isinstance(mdata, (bytes, bytearray)):
manufacturer_data = bytes(mdata)
elif isinstance(mdata, dbus.Array):
manufacturer_data = bytes(mdata)
elif isinstance(mdata, (list, tuple)):
manufacturer_data = bytes(mdata)
elif isinstance(mdata, str):
manufacturer_data = bytes.fromhex(mdata)
except (TypeError, ValueError) as e:
logger.debug(f"Could not convert manufacturer data: {e}")
break
# Extract service UUIDs
@@ -319,8 +329,17 @@ class DBusScanner:
service_data = {}
if 'ServiceData' in props:
for uuid, data in props['ServiceData'].items():
if isinstance(data, dbus.Array):
service_data[str(uuid)] = bytes(data)
try:
if isinstance(data, (bytes, bytearray)):
service_data[str(uuid)] = bytes(data)
elif isinstance(data, dbus.Array):
service_data[str(uuid)] = bytes(data)
elif isinstance(data, (list, tuple)):
service_data[str(uuid)] = bytes(data)
elif isinstance(data, str):
service_data[str(uuid)] = bytes.fromhex(data)
except (TypeError, ValueError) as e:
logger.debug(f"Could not convert service data for {uuid}: {e}")
# Extract Class of Device (Classic BT)
class_of_device = None

View File

@@ -1157,6 +1157,30 @@ def reset_identity_engine() -> None:
_identity_engine = DeviceIdentityEngine()
def _convert_to_bytes(value) -> Optional[bytes]:
"""Convert various data types to bytes safely."""
if value is None:
return None
if isinstance(value, bytes):
return value
if isinstance(value, bytearray):
return bytes(value)
if isinstance(value, str):
# Assume hex string
try:
return bytes.fromhex(value)
except ValueError:
# Not a valid hex string, encode as UTF-8
return value.encode('utf-8')
if isinstance(value, (list, tuple)):
# Array of integers (like dbus.Array)
try:
return bytes(value)
except (TypeError, ValueError):
return None
return None
def ingest_ble_dict(data: dict) -> DeviceSession:
"""
Ingest BLE observation from dictionary.
@@ -1173,9 +1197,9 @@ def ingest_ble_dict(data: dict) -> DeviceSession:
adv_type=data.get('adv_type', 'unknown'),
adv_flags=data.get('adv_flags'),
manufacturer_id=data.get('manufacturer_id'),
manufacturer_data=bytes.fromhex(data['manufacturer_data']) if data.get('manufacturer_data') else None,
manufacturer_data=_convert_to_bytes(data.get('manufacturer_data')),
service_uuids=data.get('service_uuids', []),
service_data=bytes.fromhex(data['service_data']) if data.get('service_data') else None,
service_data=_convert_to_bytes(data.get('service_data')),
local_name=data.get('local_name', data.get('name')),
appearance=data.get('appearance'),
packet_length=data.get('packet_length'),

View File

@@ -265,6 +265,36 @@ class UnifiedWiFiScanner:
pass
return False
def _is_monitor_mode_interface(self, interface: str) -> bool:
"""
Check if interface is currently in monitor mode.
Returns True if:
- Interface name ends with 'mon' (common convention)
- iw reports type as 'monitor'
"""
# Quick check by name convention
if interface.endswith('mon'):
return True
# Check actual mode via iw
if shutil.which('iw'):
try:
result = subprocess.run(
['iw', interface, 'info'],
capture_output=True,
text=True,
timeout=TOOL_TIMEOUT_DETECT,
)
if result.returncode == 0:
# Look for "type monitor" in output
if re.search(r'type\s+monitor', result.stdout, re.IGNORECASE):
return True
except Exception:
pass
return False
# =========================================================================
# Quick Scan
# =========================================================================
@@ -299,6 +329,17 @@ class UnifiedWiFiScanner:
result.interface = iface
# Check if interface is in monitor mode (can't use quick scan tools on monitor interfaces)
if self._is_monitor_mode_interface(iface):
result.error = (
f"Interface '{iface}' appears to be in monitor mode. "
"Quick scan requires a managed mode interface. "
"Either use a different interface, disable monitor mode, or use deep_scan() with airodump-ng."
)
result.is_complete = True
result.warnings.append("Monitor mode interfaces don't support standard WiFi scanning")
return result
# Select and run parser based on platform/tools
# Try multiple tools with fallback on Linux
observations = []