From fcabd7afee40bf833dd14a529cda73a95becc034 Mon Sep 17 00:00:00 2001 From: Moura <147643094+Rodrigorm33@users.noreply.github.com> Date: Thu, 30 Jul 2026 07:38:33 -0300 Subject: [PATCH] casper_glow: reject non-Casper Glow Bluetooth discoveries before config-flow handshake (#174242) Co-authored-by: Joost Lekkerkerker --- .../components/casper_glow/config_flow.py | 21 ++++++--- .../components/casper_glow/strings.json | 1 + tests/components/casper_glow/__init__.py | 43 +++++++++++++++++++ .../casper_glow/test_config_flow.py | 21 ++++++++- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/casper_glow/config_flow.py b/homeassistant/components/casper_glow/config_flow.py index c4787bb0487e..89c8e9e16a76 100644 --- a/homeassistant/components/casper_glow/config_flow.py +++ b/homeassistant/components/casper_glow/config_flow.py @@ -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 diff --git a/homeassistant/components/casper_glow/strings.json b/homeassistant/components/casper_glow/strings.json index 074ecc35a779..35ce4c0afedc 100644 --- a/homeassistant/components/casper_glow/strings.json +++ b/homeassistant/components/casper_glow/strings.json @@ -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": { diff --git a/tests/components/casper_glow/__init__.py b/tests/components/casper_glow/__init__.py index 3320453fc017..f831544654a7 100644 --- a/tests/components/casper_glow/__init__.py +++ b/tests/components/casper_glow/__init__.py @@ -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.""" diff --git a/tests/components/casper_glow/test_config_flow.py b/tests/components/casper_glow/test_config_flow.py index 8412528fae4a..328aacfefc43 100644 --- a/tests/components/casper_glow/test_config_flow.py +++ b/tests/components/casper_glow/test_config_flow.py @@ -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: