Handle homekit accessories where the pairing flag is wrong (#53385)

This commit is contained in:
Jc2k 2021-07-24 07:03:44 +01:00 committed by GitHub
parent bf3a16eed4
commit 0db160e372
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 6 deletions

View File

@ -3,6 +3,7 @@ import logging
import re
import aiohomekit
from aiohomekit.exceptions import AuthenticationError
import voluptuous as vol
from homeassistant import config_entries
@ -270,7 +271,29 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
# invalid. Remove it automatically.
existing = find_existing_host(self.hass, hkid)
if not paired and existing:
await self.hass.config_entries.async_remove(existing.entry_id)
if self.controller is None:
await self._async_setup_controller()
pairing = self.controller.load_pairing(
existing.data["AccessoryPairingID"], dict(existing.data)
)
try:
await pairing.list_accessories_and_characteristics()
_LOGGER.debug(
"%s (%s - %s) claims to be unpaired but isn't. It's implementation of HomeKit is defective or a zeroconf relay is broadcasting stale data",
name,
model,
hkid,
)
return self.async_abort(reason="already_paired")
except AuthenticationError:
_LOGGER.debug(
"%s (%s - %s) is unpaired. Removing invalid pairing for this device",
name,
model,
hkid,
)
await self.hass.config_entries.async_remove(existing.entry_id)
# Set unique-id and error out if it's already configured
self._abort_if_unique_id_configured(updates=updated_ip_port)

View File

@ -4,6 +4,7 @@ import unittest.mock
from unittest.mock import AsyncMock, patch
import aiohomekit
from aiohomekit.exceptions import AuthenticationError
from aiohomekit.model import Accessories, Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
@ -351,8 +352,48 @@ async def test_discovery_does_not_ignore_non_homekit(hass, controller):
assert result["type"] == "form"
async def test_discovery_broken_pairing_flag(hass, controller):
"""
There is already a config entry for the pairing and its pairing flag is wrong in zeroconf.
We have seen this particular implementation error in 2 different devices.
"""
await controller.add_paired_device(Accessories(), "00:00:00:00:00:00")
MockConfigEntry(
domain="homekit_controller",
data={"AccessoryPairingID": "00:00:00:00:00:00"},
unique_id="00:00:00:00:00:00",
).add_to_hass(hass)
# We just added a mock config entry so it must be visible in hass
assert len(hass.config_entries.async_entries()) == 1
device = setup_mock_accessory(controller)
discovery_info = get_device_discovery_info(device)
# Make sure that we are pairable
assert discovery_info["properties"]["sf"] != 0x0
# Device is discovered
result = await hass.config_entries.flow.async_init(
"homekit_controller",
context={"source": config_entries.SOURCE_ZEROCONF},
data=discovery_info,
)
# Should still be paired.
config_entry_count = len(hass.config_entries.async_entries())
assert config_entry_count == 1
# Even though discovered as pairable, we bail out as already paired.
assert result["reason"] == "already_paired"
async def test_discovery_invalid_config_entry(hass, controller):
"""There is already a config entry for the pairing id but it's invalid."""
pairing = await controller.add_paired_device(Accessories(), "00:00:00:00:00:00")
MockConfigEntry(
domain="homekit_controller",
data={"AccessoryPairingID": "00:00:00:00:00:00"},
@ -366,11 +407,16 @@ async def test_discovery_invalid_config_entry(hass, controller):
discovery_info = get_device_discovery_info(device)
# Device is discovered
result = await hass.config_entries.flow.async_init(
"homekit_controller",
context={"source": config_entries.SOURCE_ZEROCONF},
data=discovery_info,
)
with patch.object(
pairing,
"list_accessories_and_characteristics",
side_effect=AuthenticationError("Invalid pairing keys"),
):
result = await hass.config_entries.flow.async_init(
"homekit_controller",
context={"source": config_entries.SOURCE_ZEROCONF},
data=discovery_info,
)
# Discovery of a HKID that is in a pairable state but for which there is
# already a config entry - in that case the stale config entry is