From ac15f2cf9dfc30f107c22a3c3797731b2dfaa1ee Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 8 Nov 2022 04:15:16 -0600 Subject: [PATCH] Fix homekit bridge iid allocations (#81613) fixes undefined --- homeassistant/components/homekit/__init__.py | 10 +- .../components/homekit/accessories.py | 8 +- .../components/homekit/diagnostics.py | 2 + .../components/homekit/iidmanager.py | 72 ++++- tests/components/homekit/conftest.py | 8 +- tests/components/homekit/fixtures/iids_v1 | 249 ++++++++++++++++ .../homekit/fixtures/iids_v1_with_underscore | 50 ++++ tests/components/homekit/fixtures/iids_v2 | 273 ++++++++++++++++++ .../homekit/fixtures/iids_v2_with_underscore | 54 ++++ tests/components/homekit/test_accessories.py | 22 +- tests/components/homekit/test_diagnostics.py | 28 ++ tests/components/homekit/test_homekit.py | 6 +- tests/components/homekit/test_iidmanager.py | 83 +++++- 13 files changed, 806 insertions(+), 59 deletions(-) create mode 100644 tests/components/homekit/fixtures/iids_v1 create mode 100644 tests/components/homekit/fixtures/iids_v1_with_underscore create mode 100644 tests/components/homekit/fixtures/iids_v2 create mode 100644 tests/components/homekit/fixtures/iids_v2_with_underscore diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 333d6052d17..ca73c7dc242 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -76,13 +76,7 @@ from . import ( # noqa: F401 type_switches, type_thermostats, ) -from .accessories import ( - HomeAccessory, - HomeBridge, - HomeDriver, - HomeIIDManager, - get_accessory, -) +from .accessories import HomeAccessory, HomeBridge, HomeDriver, get_accessory from .aidmanager import AccessoryAidStorage from .const import ( ATTR_INTEGRATION, @@ -551,7 +545,7 @@ class HomeKit: async_zeroconf_instance=async_zeroconf_instance, zeroconf_server=f"{uuid}-hap.local.", loader=get_loader(), - iid_manager=HomeIIDManager(self.iid_storage), + iid_storage=self.iid_storage, ) # If we do not load the mac address will be wrong diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 61c2e3cd5dd..44b73b41ded 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -270,7 +270,7 @@ class HomeAccessory(Accessory): # type: ignore[misc] driver=driver, display_name=cleanup_name_for_homekit(name), aid=aid, - iid_manager=driver.iid_manager, + iid_manager=HomeIIDManager(driver.iid_storage), *args, **kwargs, ) @@ -570,7 +570,7 @@ class HomeBridge(Bridge): # type: ignore[misc] def __init__(self, hass: HomeAssistant, driver: HomeDriver, name: str) -> None: """Initialize a Bridge object.""" - super().__init__(driver, name, iid_manager=driver.iid_manager) + super().__init__(driver, name, iid_manager=HomeIIDManager(driver.iid_storage)) self.set_info_service( firmware_revision=format_version(__version__), manufacturer=MANUFACTURER, @@ -603,7 +603,7 @@ class HomeDriver(AccessoryDriver): # type: ignore[misc] entry_id: str, bridge_name: str, entry_title: str, - iid_manager: HomeIIDManager, + iid_storage: AccessoryIIDStorage, **kwargs: Any, ) -> None: """Initialize a AccessoryDriver object.""" @@ -612,7 +612,7 @@ class HomeDriver(AccessoryDriver): # type: ignore[misc] self._entry_id = entry_id self._bridge_name = bridge_name self._entry_title = entry_title - self.iid_manager = iid_manager + self.iid_storage = iid_storage @pyhap_callback # type: ignore[misc] def pair( diff --git a/homeassistant/components/homekit/diagnostics.py b/homeassistant/components/homekit/diagnostics.py index dbd40c1d6f5..1d0bfb92fcc 100644 --- a/homeassistant/components/homekit/diagnostics.py +++ b/homeassistant/components/homekit/diagnostics.py @@ -31,6 +31,8 @@ async def async_get_config_entry_diagnostics( "options": dict(entry.options), }, } + if homekit.iid_storage: + data["iid_storage"] = homekit.iid_storage.allocations if not homekit.driver: # not started yet or startup failed return data driver: AccessoryDriver = homekit.driver diff --git a/homeassistant/components/homekit/iidmanager.py b/homeassistant/components/homekit/iidmanager.py index 1b5cc7d6722..3805748225a 100644 --- a/homeassistant/components/homekit/iidmanager.py +++ b/homeassistant/components/homekit/iidmanager.py @@ -17,7 +17,7 @@ from homeassistant.helpers.storage import Store from .util import get_iid_storage_filename_for_entry_id -IID_MANAGER_STORAGE_VERSION = 1 +IID_MANAGER_STORAGE_VERSION = 2 IID_MANAGER_SAVE_DELAY = 2 ALLOCATIONS_KEY = "allocations" @@ -26,6 +26,40 @@ IID_MIN = 1 IID_MAX = 18446744073709551615 +ACCESSORY_INFORMATION_SERVICE = "3E" + + +class IIDStorage(Store): + """Storage class for IIDManager.""" + + async def _async_migrate_func( + self, + old_major_version: int, + old_minor_version: int, + old_data: dict, + ): + """Migrate to the new version.""" + if old_major_version == 1: + # Convert v1 to v2 format which uses a unique iid set per accessory + # instead of per pairing since we need the ACCESSORY_INFORMATION_SERVICE + # to always have iid 1 for each bridged accessory as well as the bridge + old_allocations: dict[str, int] = old_data.pop(ALLOCATIONS_KEY, {}) + new_allocation: dict[str, dict[str, int]] = {} + old_data[ALLOCATIONS_KEY] = new_allocation + for allocation_key, iid in old_allocations.items(): + aid_str, new_allocation_key = allocation_key.split("_", 1) + service_type, _, char_type, *_ = new_allocation_key.split("_") + accessory_allocation = new_allocation.setdefault(aid_str, {}) + if service_type == ACCESSORY_INFORMATION_SERVICE and not char_type: + accessory_allocation[new_allocation_key] = 1 + elif iid != 1: + accessory_allocation[new_allocation_key] = iid + + return old_data + + raise NotImplementedError + + class AccessoryIIDStorage: """ Provide stable allocation of IIDs for the lifetime of an accessory. @@ -37,15 +71,15 @@ class AccessoryIIDStorage: def __init__(self, hass: HomeAssistant, entry_id: str) -> None: """Create a new iid store.""" self.hass = hass - self.allocations: dict[str, int] = {} - self.allocated_iids: list[int] = [] + self.allocations: dict[str, dict[str, int]] = {} + self.allocated_iids: dict[str, list[int]] = {} self.entry_id = entry_id - self.store: Store | None = None + self.store: IIDStorage | None = None async def async_initialize(self) -> None: """Load the latest IID data.""" iid_store = get_iid_storage_filename_for_entry_id(self.entry_id) - self.store = Store(self.hass, IID_MANAGER_STORAGE_VERSION, iid_store) + self.store = IIDStorage(self.hass, IID_MANAGER_STORAGE_VERSION, iid_store) if not (raw_storage := await self.store.async_load()): # There is no data about iid allocations yet @@ -53,7 +87,8 @@ class AccessoryIIDStorage: assert isinstance(raw_storage, dict) self.allocations = raw_storage.get(ALLOCATIONS_KEY, {}) - self.allocated_iids = sorted(self.allocations.values()) + for aid_str, allocations in self.allocations.items(): + self.allocated_iids[aid_str] = sorted(allocations.values()) def get_or_allocate_iid( self, @@ -68,16 +103,25 @@ class AccessoryIIDStorage: char_hap_type: str | None = uuid_to_hap_type(char_uuid) if char_uuid else None # Allocation key must be a string since we are saving it to JSON allocation_key = ( - f'{aid}_{service_hap_type}_{service_unique_id or ""}_' + f'{service_hap_type}_{service_unique_id or ""}_' f'{char_hap_type or ""}_{char_unique_id or ""}' ) - if allocation_key in self.allocations: - return self.allocations[allocation_key] - next_iid = self.allocated_iids[-1] + 1 if self.allocated_iids else 1 - self.allocations[allocation_key] = next_iid - self.allocated_iids.append(next_iid) + # AID must be a string since JSON keys cannot be int + aid_str = str(aid) + accessory_allocation = self.allocations.setdefault(aid_str, {}) + accessory_allocated_iids = self.allocated_iids.setdefault(aid_str, []) + if service_hap_type == ACCESSORY_INFORMATION_SERVICE and char_uuid is None: + allocated_iid = 1 + elif allocation_key in accessory_allocation: + return accessory_allocation[allocation_key] + elif accessory_allocated_iids: + allocated_iid = accessory_allocated_iids[-1] + 1 + else: + allocated_iid = 2 + accessory_allocation[allocation_key] = allocated_iid + accessory_allocated_iids.append(allocated_iid) self._async_schedule_save() - return next_iid + return allocated_iid @callback def _async_schedule_save(self) -> None: @@ -91,6 +135,6 @@ class AccessoryIIDStorage: return await self.store.async_save(self._data_to_save()) @callback - def _data_to_save(self) -> dict[str, dict[str, int]]: + def _data_to_save(self) -> dict[str, dict[str, dict[str, int]]]: """Return data of entity map to store in a file.""" return {ALLOCATIONS_KEY: self.allocations} diff --git a/tests/components/homekit/conftest.py b/tests/components/homekit/conftest.py index 7b79e0f9b6b..b0422a40f72 100644 --- a/tests/components/homekit/conftest.py +++ b/tests/components/homekit/conftest.py @@ -6,7 +6,7 @@ from unittest.mock import patch import pytest from homeassistant.components.device_tracker.legacy import YAML_DEVICES -from homeassistant.components.homekit.accessories import HomeDriver, HomeIIDManager +from homeassistant.components.homekit.accessories import HomeDriver from homeassistant.components.homekit.const import BRIDGE_NAME, EVENT_HOMEKIT_CHANGED from homeassistant.components.homekit.iidmanager import AccessoryIIDStorage @@ -39,7 +39,7 @@ def run_driver(hass, loop, iid_storage): entry_id="", entry_title="mock entry", bridge_name=BRIDGE_NAME, - iid_manager=HomeIIDManager(iid_storage), + iid_storage=iid_storage, address="127.0.0.1", loop=loop, ) @@ -63,7 +63,7 @@ def hk_driver(hass, loop, iid_storage): entry_id="", entry_title="mock entry", bridge_name=BRIDGE_NAME, - iid_manager=HomeIIDManager(iid_storage), + iid_storage=iid_storage, address="127.0.0.1", loop=loop, ) @@ -91,7 +91,7 @@ def mock_hap(hass, loop, iid_storage, mock_zeroconf): entry_id="", entry_title="mock entry", bridge_name=BRIDGE_NAME, - iid_manager=HomeIIDManager(iid_storage), + iid_storage=iid_storage, address="127.0.0.1", loop=loop, ) diff --git a/tests/components/homekit/fixtures/iids_v1 b/tests/components/homekit/fixtures/iids_v1 new file mode 100644 index 00000000000..1da11d8de67 --- /dev/null +++ b/tests/components/homekit/fixtures/iids_v1 @@ -0,0 +1,249 @@ +{ + "version": 1, + "minor_version": 1, + "key": "homekit.v1.iids", + "data": { + "allocations": { + "1_3E___": 1, + "1_3E__14_": 2, + "1_3E__20_": 3, + "1_3E__21_": 4, + "1_3E__23_": 5, + "1_3E__30_": 6, + "1_3E__52_": 7, + "1_A2___": 8, + "1_A2__37_": 9, + "935391877_3E___": 10, + "935391877_3E__14_": 11, + "935391877_3E__20_": 12, + "935391877_3E__21_": 13, + "935391877_3E__23_": 14, + "935391877_3E__30_": 15, + "935391877_3E__52_": 16, + "935391877_4A___": 17, + "935391877_4A__F_": 18, + "935391877_4A__33_": 19, + "935391877_4A__11_": 20, + "935391877_4A__35_": 21, + "935391877_4A__36_": 22, + "935391877_4A__D_": 23, + "935391877_4A__12_": 24, + "935391877_4A__34_": 25, + "935391877_4A__10_": 26, + "935391877_B7___": 27, + "935391877_B7__B0_": 28, + "935391877_B7__BF_": 29, + "935391877_B7__AF_": 30, + "985724734_3E___": 31, + "985724734_3E__14_": 32, + "985724734_3E__20_": 33, + "985724734_3E__21_": 34, + "985724734_3E__23_": 35, + "985724734_3E__30_": 36, + "985724734_3E__52_": 37, + "985724734_4A___": 38, + "985724734_4A__F_": 39, + "985724734_4A__33_": 40, + "985724734_4A__11_": 41, + "985724734_4A__35_": 42, + "985724734_4A__36_": 43, + "985724734_4A__D_": 44, + "985724734_4A__12_": 45, + "985724734_4A__34_": 46, + "985724734_4A__10_": 47, + "985724734_B7___": 48, + "985724734_B7__B0_": 49, + "985724734_B7__BF_": 50, + "985724734_B7__AF_": 51, + "3083074204_3E___": 52, + "3083074204_3E__14_": 53, + "3083074204_3E__20_": 54, + "3083074204_3E__21_": 55, + "3083074204_3E__23_": 56, + "3083074204_3E__30_": 57, + "3083074204_3E__52_": 58, + "3083074204_4A___": 59, + "3083074204_4A__F_": 60, + "3083074204_4A__33_": 61, + "3083074204_4A__11_": 62, + "3083074204_4A__35_": 63, + "3083074204_4A__36_": 64, + "3083074204_4A__D_": 65, + "3083074204_4A__12_": 66, + "3083074204_4A__34_": 67, + "3083074204_4A__10_": 68, + "3083074204_B7___": 69, + "3083074204_B7__B0_": 70, + "3083074204_B7__BF_": 71, + "3083074204_B7__AF_": 72, + "3032741347_3E___": 73, + "3032741347_3E__14_": 74, + "3032741347_3E__20_": 75, + "3032741347_3E__21_": 76, + "3032741347_3E__23_": 77, + "3032741347_3E__30_": 78, + "3032741347_3E__52_": 79, + "3032741347_4A___": 80, + "3032741347_4A__F_": 81, + "3032741347_4A__33_": 82, + "3032741347_4A__11_": 83, + "3032741347_4A__35_": 84, + "3032741347_4A__36_": 85, + "3032741347_4A__D_": 86, + "3032741347_4A__12_": 87, + "3032741347_4A__34_": 88, + "3032741347_4A__10_": 89, + "3032741347_B7___": 90, + "3032741347_B7__B0_": 91, + "3032741347_B7__BF_": 92, + "3032741347_B7__AF_": 93, + "3681509609_3E___": 94, + "3681509609_3E__14_": 95, + "3681509609_3E__20_": 96, + "3681509609_3E__21_": 97, + "3681509609_3E__23_": 98, + "3681509609_3E__30_": 99, + "3681509609_3E__52_": 100, + "3681509609_4A___": 101, + "3681509609_4A__F_": 102, + "3681509609_4A__33_": 103, + "3681509609_4A__11_": 104, + "3681509609_4A__35_": 105, + "3681509609_4A__36_": 106, + "3681509609_4A__D_": 107, + "3681509609_4A__12_": 108, + "3681509609_4A__34_": 109, + "3681509609_4A__10_": 110, + "3681509609_B7___": 111, + "3681509609_B7__B0_": 112, + "3681509609_B7__BF_": 113, + "3681509609_B7__AF_": 114, + "3866063418_3E___": 115, + "3866063418_3E__14_": 116, + "3866063418_3E__20_": 117, + "3866063418_3E__21_": 118, + "3866063418_3E__23_": 119, + "3866063418_3E__30_": 120, + "3866063418_3E__52_": 121, + "3866063418_4A___": 122, + "3866063418_4A__F_": 123, + "3866063418_4A__33_": 124, + "3866063418_4A__11_": 125, + "3866063418_4A__35_": 126, + "3866063418_4A__36_": 127, + "3866063418_4A__D_": 128, + "3866063418_4A__12_": 129, + "3866063418_4A__34_": 130, + "3866063418_4A__10_": 131, + "3866063418_B7___": 132, + "3866063418_B7__B0_": 133, + "3866063418_B7__BF_": 134, + "3866063418_B7__AF_": 135, + "3239498961_3E___": 136, + "3239498961_3E__14_": 137, + "3239498961_3E__20_": 138, + "3239498961_3E__21_": 139, + "3239498961_3E__23_": 140, + "3239498961_3E__30_": 141, + "3239498961_3E__52_": 142, + "3239498961_4A___": 143, + "3239498961_4A__F_": 144, + "3239498961_4A__33_": 145, + "3239498961_4A__11_": 146, + "3239498961_4A__35_": 147, + "3239498961_4A__36_": 148, + "3239498961_4A__D_": 149, + "3239498961_4A__12_": 150, + "3239498961_4A__34_": 151, + "3239498961_4A__10_": 152, + "3239498961_B7___": 153, + "3239498961_B7__B0_": 154, + "3239498961_B7__BF_": 155, + "3239498961_B7__AF_": 156, + "3289831818_3E___": 157, + "3289831818_3E__14_": 158, + "3289831818_3E__20_": 159, + "3289831818_3E__21_": 160, + "3289831818_3E__23_": 161, + "3289831818_3E__30_": 162, + "3289831818_3E__52_": 163, + "3289831818_4A___": 164, + "3289831818_4A__F_": 165, + "3289831818_4A__33_": 166, + "3289831818_4A__11_": 167, + "3289831818_4A__35_": 168, + "3289831818_4A__36_": 169, + "3289831818_4A__D_": 170, + "3289831818_4A__12_": 171, + "3289831818_4A__34_": 172, + "3289831818_4A__10_": 173, + "3289831818_B7___": 174, + "3289831818_B7__B0_": 175, + "3289831818_B7__BF_": 176, + "3289831818_B7__AF_": 177, + "3071722771_3E___": 178, + "3071722771_3E__14_": 179, + "3071722771_3E__20_": 180, + "3071722771_3E__21_": 181, + "3071722771_3E__23_": 182, + "3071722771_3E__30_": 183, + "3071722771_3E__52_": 184, + "3071722771_4A___": 185, + "3071722771_4A__F_": 186, + "3071722771_4A__33_": 187, + "3071722771_4A__11_": 188, + "3071722771_4A__35_": 189, + "3071722771_4A__36_": 190, + "3071722771_4A__D_": 191, + "3071722771_4A__12_": 192, + "3071722771_4A__34_": 193, + "3071722771_4A__10_": 194, + "3071722771_B7___": 195, + "3071722771_B7__B0_": 196, + "3071722771_B7__BF_": 197, + "3071722771_B7__AF_": 198, + "3391630365_3E___": 199, + "3391630365_3E__14_": 200, + "3391630365_3E__20_": 201, + "3391630365_3E__21_": 202, + "3391630365_3E__23_": 203, + "3391630365_3E__30_": 204, + "3391630365_3E__52_": 205, + "3391630365_4A___": 206, + "3391630365_4A__F_": 207, + "3391630365_4A__33_": 208, + "3391630365_4A__11_": 209, + "3391630365_4A__35_": 210, + "3391630365_4A__36_": 211, + "3391630365_4A__D_": 212, + "3391630365_4A__12_": 213, + "3391630365_4A__34_": 214, + "3391630365_4A__10_": 215, + "3391630365_B7___": 216, + "3391630365_B7__B0_": 217, + "3391630365_B7__BF_": 218, + "3391630365_B7__AF_": 219, + "3274187032_3E___": 220, + "3274187032_3E__14_": 221, + "3274187032_3E__20_": 222, + "3274187032_3E__21_": 223, + "3274187032_3E__23_": 224, + "3274187032_3E__30_": 225, + "3274187032_3E__52_": 226, + "3274187032_4A___": 227, + "3274187032_4A__F_": 228, + "3274187032_4A__33_": 229, + "3274187032_4A__11_": 230, + "3274187032_4A__35_": 231, + "3274187032_4A__36_": 232, + "3274187032_4A__D_": 233, + "3274187032_4A__12_": 234, + "3274187032_4A__34_": 235, + "3274187032_4A__10_": 236, + "3274187032_B7___": 237, + "3274187032_B7__B0_": 238, + "3274187032_B7__BF_": 239, + "3274187032_B7__AF_": 240 + } + } +} diff --git a/tests/components/homekit/fixtures/iids_v1_with_underscore b/tests/components/homekit/fixtures/iids_v1_with_underscore new file mode 100644 index 00000000000..844c17a4746 --- /dev/null +++ b/tests/components/homekit/fixtures/iids_v1_with_underscore @@ -0,0 +1,50 @@ +{ + "version": 1, + "minor_version": 1, + "key": "homekit.8a47205bd97c07d7a908f10166ebe636.iids", + "data": { + "allocations": { + "1_3E___": 1, + "1_3E__14_": 2, + "1_3E__20_": 3, + "1_3E__21_": 4, + "1_3E__23_": 5, + "1_3E__30_": 6, + "1_3E__52_": 7, + "1_A2___": 8, + "1_A2__37_": 9, + "1973560704_3E___": 10, + "1973560704_3E__14_": 11, + "1973560704_3E__20_": 12, + "1973560704_3E__21_": 13, + "1973560704_3E__23_": 14, + "1973560704_3E__30_": 15, + "1973560704_3E__52_": 16, + "1973560704_3E__53_": 17, + "1973560704_89_pressed-__": 18, + "1973560704_89_pressed-_73_": 19, + "1973560704_89_pressed-_23_": 20, + "1973560704_89_pressed-_CB_": 21, + "1973560704_CC_pressed-__": 22, + "1973560704_CC_pressed-_CD_": 23, + "1973560704_89_changed_states-__": 24, + "1973560704_89_changed_states-_73_": 25, + "1973560704_89_changed_states-_23_": 26, + "1973560704_89_changed_states-_CB_": 27, + "1973560704_CC_changed_states-__": 28, + "1973560704_CC_changed_states-_CD_": 29, + "1973560704_89_turned_off-__": 30, + "1973560704_89_turned_off-_73_": 31, + "1973560704_89_turned_off-_23_": 32, + "1973560704_89_turned_off-_CB_": 33, + "1973560704_CC_turned_off-__": 34, + "1973560704_CC_turned_off-_CD_": 35, + "1973560704_89_turned_on-__": 36, + "1973560704_89_turned_on-_73_": 37, + "1973560704_89_turned_on-_23_": 38, + "1973560704_89_turned_on-_CB_": 39, + "1973560704_CC_turned_on-__": 40, + "1973560704_CC_turned_on-_CD_": 41 + } + } +} \ No newline at end of file diff --git a/tests/components/homekit/fixtures/iids_v2 b/tests/components/homekit/fixtures/iids_v2 new file mode 100644 index 00000000000..76bff55e935 --- /dev/null +++ b/tests/components/homekit/fixtures/iids_v2 @@ -0,0 +1,273 @@ +{ + "version": 2, + "minor_version": 1, + "key": "homekit.v2.iids", + "data": { + "allocations": { + "1": { + "3E___": 1, + "3E__14_": 2, + "3E__20_": 3, + "3E__21_": 4, + "3E__23_": 5, + "3E__30_": 6, + "3E__52_": 7, + "A2___": 8, + "A2__37_": 9 + }, + "935391877": { + "3E___": 1, + "3E__14_": 11, + "3E__20_": 12, + "3E__21_": 13, + "3E__23_": 14, + "3E__30_": 15, + "3E__52_": 16, + "4A___": 17, + "4A__F_": 18, + "4A__33_": 19, + "4A__11_": 20, + "4A__35_": 21, + "4A__36_": 22, + "4A__D_": 23, + "4A__12_": 24, + "4A__34_": 25, + "4A__10_": 26, + "B7___": 27, + "B7__B0_": 28, + "B7__BF_": 29, + "B7__AF_": 30 + }, + "985724734": { + "3E___": 1, + "3E__14_": 32, + "3E__20_": 33, + "3E__21_": 34, + "3E__23_": 35, + "3E__30_": 36, + "3E__52_": 37, + "4A___": 38, + "4A__F_": 39, + "4A__33_": 40, + "4A__11_": 41, + "4A__35_": 42, + "4A__36_": 43, + "4A__D_": 44, + "4A__12_": 45, + "4A__34_": 46, + "4A__10_": 47, + "B7___": 48, + "B7__B0_": 49, + "B7__BF_": 50, + "B7__AF_": 51 + }, + "3083074204": { + "3E___": 1, + "3E__14_": 53, + "3E__20_": 54, + "3E__21_": 55, + "3E__23_": 56, + "3E__30_": 57, + "3E__52_": 58, + "4A___": 59, + "4A__F_": 60, + "4A__33_": 61, + "4A__11_": 62, + "4A__35_": 63, + "4A__36_": 64, + "4A__D_": 65, + "4A__12_": 66, + "4A__34_": 67, + "4A__10_": 68, + "B7___": 69, + "B7__B0_": 70, + "B7__BF_": 71, + "B7__AF_": 72 + }, + "3032741347": { + "3E___": 1, + "3E__14_": 74, + "3E__20_": 75, + "3E__21_": 76, + "3E__23_": 77, + "3E__30_": 78, + "3E__52_": 79, + "4A___": 80, + "4A__F_": 81, + "4A__33_": 82, + "4A__11_": 83, + "4A__35_": 84, + "4A__36_": 85, + "4A__D_": 86, + "4A__12_": 87, + "4A__34_": 88, + "4A__10_": 89, + "B7___": 90, + "B7__B0_": 91, + "B7__BF_": 92, + "B7__AF_": 93 + }, + "3681509609": { + "3E___": 1, + "3E__14_": 95, + "3E__20_": 96, + "3E__21_": 97, + "3E__23_": 98, + "3E__30_": 99, + "3E__52_": 100, + "4A___": 101, + "4A__F_": 102, + "4A__33_": 103, + "4A__11_": 104, + "4A__35_": 105, + "4A__36_": 106, + "4A__D_": 107, + "4A__12_": 108, + "4A__34_": 109, + "4A__10_": 110, + "B7___": 111, + "B7__B0_": 112, + "B7__BF_": 113, + "B7__AF_": 114 + }, + "3866063418": { + "3E___": 1, + "3E__14_": 116, + "3E__20_": 117, + "3E__21_": 118, + "3E__23_": 119, + "3E__30_": 120, + "3E__52_": 121, + "4A___": 122, + "4A__F_": 123, + "4A__33_": 124, + "4A__11_": 125, + "4A__35_": 126, + "4A__36_": 127, + "4A__D_": 128, + "4A__12_": 129, + "4A__34_": 130, + "4A__10_": 131, + "B7___": 132, + "B7__B0_": 133, + "B7__BF_": 134, + "B7__AF_": 135 + }, + "3239498961": { + "3E___": 1, + "3E__14_": 137, + "3E__20_": 138, + "3E__21_": 139, + "3E__23_": 140, + "3E__30_": 141, + "3E__52_": 142, + "4A___": 143, + "4A__F_": 144, + "4A__33_": 145, + "4A__11_": 146, + "4A__35_": 147, + "4A__36_": 148, + "4A__D_": 149, + "4A__12_": 150, + "4A__34_": 151, + "4A__10_": 152, + "B7___": 153, + "B7__B0_": 154, + "B7__BF_": 155, + "B7__AF_": 156 + }, + "3289831818": { + "3E___": 1, + "3E__14_": 158, + "3E__20_": 159, + "3E__21_": 160, + "3E__23_": 161, + "3E__30_": 162, + "3E__52_": 163, + "4A___": 164, + "4A__F_": 165, + "4A__33_": 166, + "4A__11_": 167, + "4A__35_": 168, + "4A__36_": 169, + "4A__D_": 170, + "4A__12_": 171, + "4A__34_": 172, + "4A__10_": 173, + "B7___": 174, + "B7__B0_": 175, + "B7__BF_": 176, + "B7__AF_": 177 + }, + "3071722771": { + "3E___": 1, + "3E__14_": 179, + "3E__20_": 180, + "3E__21_": 181, + "3E__23_": 182, + "3E__30_": 183, + "3E__52_": 184, + "4A___": 185, + "4A__F_": 186, + "4A__33_": 187, + "4A__11_": 188, + "4A__35_": 189, + "4A__36_": 190, + "4A__D_": 191, + "4A__12_": 192, + "4A__34_": 193, + "4A__10_": 194, + "B7___": 195, + "B7__B0_": 196, + "B7__BF_": 197, + "B7__AF_": 198 + }, + "3391630365": { + "3E___": 1, + "3E__14_": 200, + "3E__20_": 201, + "3E__21_": 202, + "3E__23_": 203, + "3E__30_": 204, + "3E__52_": 205, + "4A___": 206, + "4A__F_": 207, + "4A__33_": 208, + "4A__11_": 209, + "4A__35_": 210, + "4A__36_": 211, + "4A__D_": 212, + "4A__12_": 213, + "4A__34_": 214, + "4A__10_": 215, + "B7___": 216, + "B7__B0_": 217, + "B7__BF_": 218, + "B7__AF_": 219 + }, + "3274187032": { + "3E___": 1, + "3E__14_": 221, + "3E__20_": 222, + "3E__21_": 223, + "3E__23_": 224, + "3E__30_": 225, + "3E__52_": 226, + "4A___": 227, + "4A__F_": 228, + "4A__33_": 229, + "4A__11_": 230, + "4A__35_": 231, + "4A__36_": 232, + "4A__D_": 233, + "4A__12_": 234, + "4A__34_": 235, + "4A__10_": 236, + "B7___": 237, + "B7__B0_": 238, + "B7__BF_": 239, + "B7__AF_": 240 + } + } + } +} diff --git a/tests/components/homekit/fixtures/iids_v2_with_underscore b/tests/components/homekit/fixtures/iids_v2_with_underscore new file mode 100644 index 00000000000..52e874e41a5 --- /dev/null +++ b/tests/components/homekit/fixtures/iids_v2_with_underscore @@ -0,0 +1,54 @@ +{ + "version": 2, + "minor_version": 1, + "key": "homekit.8a47205bd97c07d7a908f10166ebe636.iids", + "data": { + "allocations": { + "1": { + "3E___": 1, + "3E__14_": 2, + "3E__20_": 3, + "3E__21_": 4, + "3E__23_": 5, + "3E__30_": 6, + "3E__52_": 7, + "A2___": 8, + "A2__37_": 9 + }, + "1973560704": { + "3E___": 1, + "3E__14_": 11, + "3E__20_": 12, + "3E__21_": 13, + "3E__23_": 14, + "3E__30_": 15, + "3E__52_": 16, + "3E__53_": 17, + "89_pressed-__": 18, + "89_pressed-_73_": 19, + "89_pressed-_23_": 20, + "89_pressed-_CB_": 21, + "CC_pressed-__": 22, + "CC_pressed-_CD_": 23, + "89_changed_states-__": 24, + "89_changed_states-_73_": 25, + "89_changed_states-_23_": 26, + "89_changed_states-_CB_": 27, + "CC_changed_states-__": 28, + "CC_changed_states-_CD_": 29, + "89_turned_off-__": 30, + "89_turned_off-_73_": 31, + "89_turned_off-_23_": 32, + "89_turned_off-_CB_": 33, + "CC_turned_off-__": 34, + "CC_turned_off-_CD_": 35, + "89_turned_on-__": 36, + "89_turned_on-_73_": 37, + "89_turned_on-_23_": 38, + "89_turned_on-_CB_": 39, + "CC_turned_on-__": 40, + "CC_turned_on-_CD_": 41 + } + } + } +} \ No newline at end of file diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 2a0f3f2f718..36eaeff91b4 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -10,7 +10,6 @@ from homeassistant.components.homekit.accessories import ( HomeAccessory, HomeBridge, HomeDriver, - HomeIIDManager, ) from homeassistant.components.homekit.const import ( ATTR_DISPLAY_NAME, @@ -724,7 +723,7 @@ def test_home_driver(iid_storage): "entry_id", "name", "title", - iid_manager=HomeIIDManager(iid_storage), + iid_storage=iid_storage, address=ip_address, port=port, persist_file=path, @@ -752,22 +751,3 @@ def test_home_driver(iid_storage): mock_unpair.assert_called_with("client_uuid") mock_show_msg.assert_called_with("hass", "entry_id", "title (any)", pin, "X-HM://0") - - -async def test_iid_collision_raises(hass, hk_driver): - """Test iid collision raises. - - If we try to allocate the same IID to the an accessory twice, we should - raise an exception. - """ - - entity_id = "light.accessory" - entity_id2 = "light.accessory2" - - hass.states.async_set(entity_id, STATE_OFF) - hass.states.async_set(entity_id2, STATE_OFF) - - HomeAccessory(hass, hk_driver, "Home Accessory", entity_id, 2, {}) - - with pytest.raises(RuntimeError): - HomeAccessory(hass, hk_driver, "Home Accessory", entity_id2, 2, {}) diff --git a/tests/components/homekit/test_diagnostics.py b/tests/components/homekit/test_diagnostics.py index 1f6f7c584f3..be98c3bacdd 100644 --- a/tests/components/homekit/test_diagnostics.py +++ b/tests/components/homekit/test_diagnostics.py @@ -43,6 +43,19 @@ async def test_config_entry_running(hass, hass_client, hk_driver, mock_async_zer diag = await get_diagnostics_for_config_entry(hass, hass_client, entry) assert diag == { "bridge": {}, + "iid_storage": { + "1": { + "3E__14_": 2, + "3E__20_": 3, + "3E__21_": 4, + "3E__23_": 5, + "3E__30_": 6, + "3E__52_": 7, + "3E___": 1, + "A2__37_": 9, + "A2___": 8, + } + }, "accessories": [ { "aid": 1, @@ -257,6 +270,21 @@ async def test_config_entry_accessory( }, "config_version": 2, "pairing_id": ANY, + "iid_storage": { + "1": { + "3E__14_": 2, + "3E__20_": 3, + "3E__21_": 4, + "3E__23_": 5, + "3E__30_": 6, + "3E__52_": 7, + "3E___": 1, + "43__25_": 11, + "43___": 10, + "A2__37_": 9, + "A2___": 8, + } + }, "status": 1, } with patch("pyhap.accessory_driver.AccessoryDriver.async_start"), patch( diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index 21dc94a4b54..d8c02aa98c4 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -262,7 +262,7 @@ async def test_homekit_setup(hass, hk_driver, mock_async_zeroconf): async_zeroconf_instance=zeroconf_mock, zeroconf_server=f"{uuid}-hap.local.", loader=ANY, - iid_manager=ANY, + iid_storage=ANY, ) assert homekit.driver.safe_mode is False @@ -306,7 +306,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver, mock_async_zeroconf): async_zeroconf_instance=mock_async_zeroconf, zeroconf_server=f"{uuid}-hap.local.", loader=ANY, - iid_manager=ANY, + iid_storage=ANY, ) @@ -350,7 +350,7 @@ async def test_homekit_setup_advertise_ip(hass, hk_driver, mock_async_zeroconf): async_zeroconf_instance=async_zeroconf_instance, zeroconf_server=f"{uuid}-hap.local.", loader=ANY, - iid_manager=ANY, + iid_storage=ANY, ) diff --git a/tests/components/homekit/test_iidmanager.py b/tests/components/homekit/test_iidmanager.py index a791c30a341..3e4a19c9045 100644 --- a/tests/components/homekit/test_iidmanager.py +++ b/tests/components/homekit/test_iidmanager.py @@ -1,6 +1,5 @@ """Tests for the HomeKit IID manager.""" - from uuid import UUID from homeassistant.components.homekit.const import DOMAIN @@ -8,9 +7,10 @@ from homeassistant.components.homekit.iidmanager import ( AccessoryIIDStorage, get_iid_storage_filename_for_entry_id, ) +from homeassistant.helpers.json import json_loads from homeassistant.util.uuid import random_uuid_hex -from tests.common import MockConfigEntry +from tests.common import MockConfigEntry, load_fixture async def test_iid_generation_and_restore(hass, iid_storage, hass_storage): @@ -77,9 +77,6 @@ async def test_iid_generation_and_restore(hass, iid_storage, hass_storage): unique_service_unique_char_new_aid_iid1 == unique_service_unique_char_new_aid_iid2 ) - assert unique_service_unique_char_new_aid_iid1 != iid1 - assert unique_service_unique_char_new_aid_iid1 != unique_service_unique_char_iid1 - await iid_storage.async_save() iid_storage2 = AccessoryIIDStorage(hass, entry.entry_id) @@ -99,3 +96,79 @@ async def test_iid_storage_filename(hass, iid_storage, hass_storage): assert iid_storage.store.path.endswith( get_iid_storage_filename_for_entry_id(entry.entry_id) ) + + +async def test_iid_migration_to_v2(hass, iid_storage, hass_storage): + """Test iid storage migration.""" + v1_iids = json_loads(load_fixture("iids_v1", DOMAIN)) + v2_iids = json_loads(load_fixture("iids_v2", DOMAIN)) + hass_storage["homekit.v1.iids"] = v1_iids + hass_storage["homekit.v2.iids"] = v2_iids + + iid_storage_v2 = AccessoryIIDStorage(hass, "v1") + await iid_storage_v2.async_initialize() + + iid_storage_v1 = AccessoryIIDStorage(hass, "v2") + await iid_storage_v1.async_initialize() + + assert iid_storage_v1.allocations == iid_storage_v2.allocations + assert iid_storage_v1.allocated_iids == iid_storage_v2.allocated_iids + + assert len(iid_storage_v2.allocations) == 12 + + for allocations in iid_storage_v2.allocations.values(): + assert allocations["3E___"] == 1 + + +async def test_iid_migration_to_v2_with_underscore(hass, iid_storage, hass_storage): + """Test iid storage migration with underscore.""" + v1_iids = json_loads(load_fixture("iids_v1_with_underscore", DOMAIN)) + v2_iids = json_loads(load_fixture("iids_v2_with_underscore", DOMAIN)) + hass_storage["homekit.v1_with_underscore.iids"] = v1_iids + hass_storage["homekit.v2_with_underscore.iids"] = v2_iids + + iid_storage_v2 = AccessoryIIDStorage(hass, "v1_with_underscore") + await iid_storage_v2.async_initialize() + + iid_storage_v1 = AccessoryIIDStorage(hass, "v2_with_underscore") + await iid_storage_v1.async_initialize() + + assert iid_storage_v1.allocations == iid_storage_v2.allocations + assert iid_storage_v1.allocated_iids == iid_storage_v2.allocated_iids + + assert len(iid_storage_v2.allocations) == 2 + + for allocations in iid_storage_v2.allocations.values(): + assert allocations["3E___"] == 1 + + +async def test_iid_generation_and_restore_v2(hass, iid_storage, hass_storage): + """Test generating iids and restoring them from storage.""" + entry = MockConfigEntry(domain=DOMAIN) + + iid_storage = AccessoryIIDStorage(hass, entry.entry_id) + await iid_storage.async_initialize() + not_accessory_info_service_iid = iid_storage.get_or_allocate_iid( + 1, "000000AA-0000-1000-8000-0026BB765291", None, None, None + ) + assert not_accessory_info_service_iid == 2 + not_accessory_info_service_iid_2 = iid_storage.get_or_allocate_iid( + 1, "000000BB-0000-1000-8000-0026BB765291", None, None, None + ) + assert not_accessory_info_service_iid_2 == 3 + not_accessory_info_service_iid_2 = iid_storage.get_or_allocate_iid( + 1, "000000BB-0000-1000-8000-0026BB765291", None, None, None + ) + assert not_accessory_info_service_iid_2 == 3 + accessory_info_service_iid = iid_storage.get_or_allocate_iid( + 1, "0000003E-0000-1000-8000-0026BB765291", None, None, None + ) + assert accessory_info_service_iid == 1 + accessory_info_service_iid = iid_storage.get_or_allocate_iid( + 1, "0000003E-0000-1000-8000-0026BB765291", None, None, None + ) + assert accessory_info_service_iid == 1 + accessory_info_service_iid = iid_storage.get_or_allocate_iid( + 2, "0000003E-0000-1000-8000-0026BB765291", None, None, None + ) + assert accessory_info_service_iid == 1