casper_glow: reject non-Casper Glow Bluetooth discoveries before config-flow handshake (#174242)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Moura
2026-07-30 12:38:33 +02:00
committed by GitHub
co-authored by Joost Lekkerkerker
parent da7f25b244
commit 2fc167204c
4 changed files with 78 additions and 8 deletions
@@ -20,6 +20,16 @@ from .const import DOMAIN, LOCAL_NAMES
_LOGGER = logging.getLogger(__name__)
def _is_casper_glow_discovery(discovery_info: BluetoothServiceInfoBleak) -> bool:
"""Return whether the Bluetooth discovery looks like a Casper Glow."""
return bool(
discovery_info.name
and any(
discovery_info.name.startswith(local_name) for local_name in LOCAL_NAMES
)
)
class CasperGlowConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Casper Glow."""
@@ -36,6 +46,9 @@ class CasperGlowConfigFlow(ConfigFlow, domain=DOMAIN):
self, discovery_info: BluetoothServiceInfoBleak
) -> ConfigFlowResult:
"""Handle the bluetooth discovery step."""
if not _is_casper_glow_discovery(discovery_info):
return self.async_abort(reason="not_supported")
await self.async_set_unique_id(format_mac(discovery_info.address))
self._abort_if_unique_id_configured()
self._discovery_info = discovery_info
@@ -118,13 +131,7 @@ class CasperGlowConfigFlow(ConfigFlow, domain=DOMAIN):
if (
format_mac(discovery.address) in current_addresses
or discovery.address in self._discovered_devices
or not (
discovery.name
and any(
discovery.name.startswith(local_name)
for local_name in LOCAL_NAMES
)
)
or not _is_casper_glow_discovery(discovery)
):
continue
self._discovered_devices[discovery.address] = discovery
@@ -5,6 +5,7 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"not_supported": "Device not supported",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
+43
View File
@@ -27,6 +27,49 @@ CASPER_GLOW_DISCOVERY_INFO = BluetoothServiceInfoBleak(
tx_power=-127,
)
SAMSUNG_EARBUDS_ADDRESS = "72:B3:56:C4:12:B7"
SAMSUNG_EARBUDS_NAME = "Evilpigs's Buds2 Pro"
SAMSUNG_EARBUDS_MANUFACTURER_DATA = {
117: bytes.fromhex(
"020901000000150300000000000000000000000000000000000000030146015b4865616470686f6e655d20"
)
}
SAMSUNG_EARBUDS_SERVICE_UUIDS = [
"0000184e-0000-1000-8000-00805f9b34fb",
"0000184f-0000-1000-8000-00805f9b34fb",
"00001850-0000-1000-8000-00805f9b34fb",
"00001844-0000-1000-8000-00805f9b34fb",
"0000184d-0000-1000-8000-00805f9b34fb",
]
SAMSUNG_EARBUDS_SERVICE_DATA = {
"00002b51-0000-1000-8000-00805f9b34fb": bytes.fromhex("2a00"),
"00001853-0000-1000-8000-00805f9b34fb": bytes.fromhex("00"),
"0000184e-0000-1000-8000-00805f9b34fb": bytes.fromhex("006f006b0000"),
"a7a473e9-19c6-491b-aea6-7ea92b8f043a": bytes.fromhex("014652e70e"),
}
SAMSUNG_EARBUDS_DISCOVERY_INFO = BluetoothServiceInfoBleak(
name=SAMSUNG_EARBUDS_NAME,
address=SAMSUNG_EARBUDS_ADDRESS,
rssi=-82,
manufacturer_data=SAMSUNG_EARBUDS_MANUFACTURER_DATA,
service_uuids=SAMSUNG_EARBUDS_SERVICE_UUIDS,
service_data=SAMSUNG_EARBUDS_SERVICE_DATA,
source="local",
device=generate_ble_device(
address=SAMSUNG_EARBUDS_ADDRESS, name=SAMSUNG_EARBUDS_NAME
),
advertisement=generate_advertisement_data(
local_name=SAMSUNG_EARBUDS_NAME,
manufacturer_data=SAMSUNG_EARBUDS_MANUFACTURER_DATA,
service_uuids=SAMSUNG_EARBUDS_SERVICE_UUIDS,
service_data=SAMSUNG_EARBUDS_SERVICE_DATA,
),
time=0,
connectable=True,
tx_power=-127,
)
async def setup_integration(hass: HomeAssistant, entry: MockConfigEntry) -> None:
"""Set up the Casper Glow integration."""
@@ -14,7 +14,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.device_registry import format_mac
from . import CASPER_GLOW_DISCOVERY_INFO, NOT_CASPER_GLOW_DISCOVERY_INFO
from . import (
CASPER_GLOW_DISCOVERY_INFO,
NOT_CASPER_GLOW_DISCOVERY_INFO,
SAMSUNG_EARBUDS_DISCOVERY_INFO,
)
from tests.common import MockConfigEntry
from tests.components.bluetooth import (
@@ -80,6 +84,21 @@ async def test_bluetooth_confirm_error(
assert result["reason"] == reason
async def test_bluetooth_step_ignores_unrecognized_device(
hass: HomeAssistant, mock_casper_glow: MagicMock
) -> None:
"""Test bluetooth discovery aborts before handshaking with unsupported devices."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_BLUETOOTH},
data=SAMSUNG_EARBUDS_DISCOVERY_INFO,
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "not_supported"
mock_casper_glow.handshake.assert_not_called()
async def test_user_step_success(
hass: HomeAssistant, mock_casper_glow: MagicMock
) -> None: