Remember homekit aids for entities without a unique id (#34587)

* Remember homekit aids for entities without a unique id

* add backwards compat

* increase cover
This commit is contained in:
J. Nick Koston 2020-04-23 12:07:55 -05:00 committed by GitHub
parent 3ce89409d2
commit a7e8446454
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 545 additions and 22 deletions

View file

@ -25,6 +25,9 @@ AID_MANAGER_STORAGE_KEY = f"{DOMAIN}.aids"
AID_MANAGER_STORAGE_VERSION = 1
AID_MANAGER_SAVE_DELAY = 2
ALLOCATIONS_KEY = "allocations"
UNIQUE_IDS_KEY = "unique_ids"
INVALID_AIDS = (0, 1)
AID_MIN = 2
@ -46,10 +49,15 @@ def _generate_aids(unique_id: str, entity_id: str) -> int:
# Not robust against collisions
yield adler32(entity_id.encode("utf-8"))
# Use fnv1a_32 of the unique id as
# fnv1a_32 has less collisions than
# adler32
yield fnv1a_32(unique_id.encode("utf-8"))
if unique_id:
# Use fnv1a_32 of the unique id as
# fnv1a_32 has less collisions than
# adler32
yield fnv1a_32(unique_id.encode("utf-8"))
# If there is no unique id we use
# fnv1a_32 as it is unlikely to collide
yield fnv1a_32(entity_id.encode("utf-8"))
# If called again resort to random allocations.
# Given the size of the range its unlikely we'll encounter duplicates
@ -86,34 +94,40 @@ class AccessoryAidStorage:
# There is no data about aid allocations yet
return
self.allocations = raw_storage.get("unique_ids", {})
# Remove the UNIQUE_IDS_KEY in 0.112 and later
# The beta version used UNIQUE_IDS_KEY but
# since we now have entity ids in the dict
# we use ALLOCATIONS_KEY but check for
# UNIQUE_IDS_KEY in case the database has not
# been upgraded yet
self.allocations = raw_storage.get(
ALLOCATIONS_KEY, raw_storage.get(UNIQUE_IDS_KEY, {})
)
self.allocated_aids = set(self.allocations.values())
def get_or_allocate_aid_for_entity_id(self, entity_id: str):
"""Generate a stable aid for an entity id."""
entity = self._entity_registry.async_get(entity_id)
if not entity:
return self._get_or_allocate_aid(None, entity_id)
if entity:
return self._get_or_allocate_aid(
get_system_unique_id(entity), entity.entity_id
)
_LOGGER.warning(
"Entity '%s' does not have a stable unique identifier so aid allocation will be unstable and may cause collisions",
entity_id,
)
return adler32(entity_id.encode("utf-8"))
sys_unique_id = get_system_unique_id(entity)
return self._get_or_allocate_aid(sys_unique_id, entity_id)
def _get_or_allocate_aid(self, unique_id: str, entity_id: str):
"""Allocate (and return) a new aid for an accessory."""
if unique_id in self.allocations:
return self.allocations[unique_id]
# Prefer the unique_id over the
# entitiy_id
storage_key = unique_id or entity_id
if storage_key in self.allocations:
return self.allocations[storage_key]
for aid in _generate_aids(unique_id, entity_id):
if aid in INVALID_AIDS:
continue
if aid not in self.allocated_aids:
self.allocations[unique_id] = aid
self.allocations[storage_key] = aid
self.allocated_aids.add(aid)
self.async_schedule_save()
return aid
@ -122,12 +136,12 @@ class AccessoryAidStorage:
f"Unable to generate unique aid allocation for {entity_id} [{unique_id}]"
)
def delete_aid(self, unique_id: str):
def delete_aid(self, storage_key: str):
"""Delete an aid allocation."""
if unique_id not in self.allocations:
if storage_key not in self.allocations:
return
aid = self.allocations.pop(unique_id)
aid = self.allocations.pop(storage_key)
self.allocated_aids.discard(aid)
self.async_schedule_save()
@ -136,7 +150,11 @@ class AccessoryAidStorage:
"""Schedule saving the entity map cache."""
self.store.async_delay_save(self._data_to_save, AID_MANAGER_SAVE_DELAY)
async def async_save(self):
"""Save the entity map cache."""
return await self.store.async_save(self._data_to_save())
@callback
def _data_to_save(self):
"""Return data of entity map to store in a file."""
return {"unique_ids": self.allocations}
return {ALLOCATIONS_KEY: self.allocations}

View file

@ -1,12 +1,17 @@
"""Tests for the HomeKit AID manager."""
import os
from zlib import adler32
from asynctest import patch
import pytest
from homeassistant.components.homekit.aidmanager import (
AID_MANAGER_STORAGE_KEY,
AccessoryAidStorage,
get_system_unique_id,
)
from homeassistant.helpers import device_registry
from homeassistant.helpers.storage import STORAGE_DIR
from tests.common import MockConfigEntry, mock_device_registry, mock_registry
@ -118,3 +123,503 @@ async def test_aid_adler32_collision(hass, device_reg, entity_reg):
aid = aid_storage.get_or_allocate_aid_for_entity_id(ent.entity_id)
assert aid not in seen_aids
seen_aids.add(aid)
async def test_aid_generation_no_unique_ids_handles_collision(
hass, device_reg, entity_reg
):
"""Test colliding aids is stable."""
aid_storage = AccessoryAidStorage(hass)
await aid_storage.async_initialize()
seen_aids = set()
collisions = []
for light_id in range(0, 220):
entity_id = f"light.light{light_id}"
hass.states.async_set(entity_id, "on")
expected_aid = adler32(entity_id.encode("utf-8"))
aid = aid_storage.get_or_allocate_aid_for_entity_id(entity_id)
if aid != expected_aid:
collisions.append(entity_id)
assert aid not in seen_aids
seen_aids.add(aid)
assert collisions == [
"light.light201",
"light.light202",
"light.light203",
"light.light204",
"light.light205",
"light.light206",
"light.light207",
"light.light208",
"light.light209",
"light.light211",
"light.light212",
"light.light213",
"light.light214",
"light.light215",
"light.light216",
"light.light217",
"light.light218",
"light.light219",
]
assert aid_storage.allocations == {
"light.light0": 514851983,
"light.light1": 514917520,
"light.light10": 594609344,
"light.light100": 677446896,
"light.light101": 677512433,
"light.light102": 677577970,
"light.light103": 677643507,
"light.light104": 677709044,
"light.light105": 677774581,
"light.light106": 677840118,
"light.light107": 677905655,
"light.light108": 677971192,
"light.light109": 678036729,
"light.light11": 594674881,
"light.light110": 677577969,
"light.light111": 677643506,
"light.light112": 677709043,
"light.light113": 677774580,
"light.light114": 677840117,
"light.light115": 677905654,
"light.light116": 677971191,
"light.light117": 678036728,
"light.light118": 678102265,
"light.light119": 678167802,
"light.light12": 594740418,
"light.light120": 677709042,
"light.light121": 677774579,
"light.light122": 677840116,
"light.light123": 677905653,
"light.light124": 677971190,
"light.light125": 678036727,
"light.light126": 678102264,
"light.light127": 678167801,
"light.light128": 678233338,
"light.light129": 678298875,
"light.light13": 594805955,
"light.light130": 677840115,
"light.light131": 677905652,
"light.light132": 677971189,
"light.light133": 678036726,
"light.light134": 678102263,
"light.light135": 678167800,
"light.light136": 678233337,
"light.light137": 678298874,
"light.light138": 678364411,
"light.light139": 678429948,
"light.light14": 594871492,
"light.light140": 677971188,
"light.light141": 678036725,
"light.light142": 678102262,
"light.light143": 678167799,
"light.light144": 678233336,
"light.light145": 678298873,
"light.light146": 678364410,
"light.light147": 678429947,
"light.light148": 678495484,
"light.light149": 678561021,
"light.light15": 594937029,
"light.light150": 678102261,
"light.light151": 678167798,
"light.light152": 678233335,
"light.light153": 678298872,
"light.light154": 678364409,
"light.light155": 678429946,
"light.light156": 678495483,
"light.light157": 678561020,
"light.light158": 678626557,
"light.light159": 678692094,
"light.light16": 595002566,
"light.light160": 678233334,
"light.light161": 678298871,
"light.light162": 678364408,
"light.light163": 678429945,
"light.light164": 678495482,
"light.light165": 678561019,
"light.light166": 678626556,
"light.light167": 678692093,
"light.light168": 678757630,
"light.light169": 678823167,
"light.light17": 595068103,
"light.light170": 678364407,
"light.light171": 678429944,
"light.light172": 678495481,
"light.light173": 678561018,
"light.light174": 678626555,
"light.light175": 678692092,
"light.light176": 678757629,
"light.light177": 678823166,
"light.light178": 678888703,
"light.light179": 678954240,
"light.light18": 595133640,
"light.light180": 678495480,
"light.light181": 678561017,
"light.light182": 678626554,
"light.light183": 678692091,
"light.light184": 678757628,
"light.light185": 678823165,
"light.light186": 678888702,
"light.light187": 678954239,
"light.light188": 679019776,
"light.light189": 679085313,
"light.light19": 595199177,
"light.light190": 678626553,
"light.light191": 678692090,
"light.light192": 678757627,
"light.light193": 678823164,
"light.light194": 678888701,
"light.light195": 678954238,
"light.light196": 679019775,
"light.light197": 679085312,
"light.light198": 679150849,
"light.light199": 679216386,
"light.light2": 514983057,
"light.light20": 594740417,
"light.light200": 677643505,
"light.light201": 1682157970,
"light.light202": 1665380351,
"light.light203": 1648602732,
"light.light204": 1631825113,
"light.light205": 1615047494,
"light.light206": 1598269875,
"light.light207": 1581492256,
"light.light208": 1833156541,
"light.light209": 1816378922,
"light.light21": 594805954,
"light.light210": 677774578,
"light.light211": 1614900399,
"light.light212": 1631678018,
"light.light213": 1648455637,
"light.light214": 1531012304,
"light.light215": 1547789923,
"light.light216": 1564567542,
"light.light217": 1581345161,
"light.light218": 1732343732,
"light.light219": 1749121351,
"light.light22": 594871491,
"light.light23": 594937028,
"light.light24": 595002565,
"light.light25": 595068102,
"light.light26": 595133639,
"light.light27": 595199176,
"light.light28": 595264713,
"light.light29": 595330250,
"light.light3": 515048594,
"light.light30": 594871490,
"light.light31": 594937027,
"light.light32": 595002564,
"light.light33": 595068101,
"light.light34": 595133638,
"light.light35": 595199175,
"light.light36": 595264712,
"light.light37": 595330249,
"light.light38": 595395786,
"light.light39": 595461323,
"light.light4": 515114131,
"light.light40": 595002563,
"light.light41": 595068100,
"light.light42": 595133637,
"light.light43": 595199174,
"light.light44": 595264711,
"light.light45": 595330248,
"light.light46": 595395785,
"light.light47": 595461322,
"light.light48": 595526859,
"light.light49": 595592396,
"light.light5": 515179668,
"light.light50": 595133636,
"light.light51": 595199173,
"light.light52": 595264710,
"light.light53": 595330247,
"light.light54": 595395784,
"light.light55": 595461321,
"light.light56": 595526858,
"light.light57": 595592395,
"light.light58": 595657932,
"light.light59": 595723469,
"light.light6": 515245205,
"light.light60": 595264709,
"light.light61": 595330246,
"light.light62": 595395783,
"light.light63": 595461320,
"light.light64": 595526857,
"light.light65": 595592394,
"light.light66": 595657931,
"light.light67": 595723468,
"light.light68": 595789005,
"light.light69": 595854542,
"light.light7": 515310742,
"light.light70": 595395782,
"light.light71": 595461319,
"light.light72": 595526856,
"light.light73": 595592393,
"light.light74": 595657930,
"light.light75": 595723467,
"light.light76": 595789004,
"light.light77": 595854541,
"light.light78": 595920078,
"light.light79": 595985615,
"light.light8": 515376279,
"light.light80": 595526855,
"light.light81": 595592392,
"light.light82": 595657929,
"light.light83": 595723466,
"light.light84": 595789003,
"light.light85": 595854540,
"light.light86": 595920077,
"light.light87": 595985614,
"light.light88": 596051151,
"light.light89": 596116688,
"light.light9": 515441816,
"light.light90": 595657928,
"light.light91": 595723465,
"light.light92": 595789002,
"light.light93": 595854539,
"light.light94": 595920076,
"light.light95": 595985613,
"light.light96": 596051150,
"light.light97": 596116687,
"light.light98": 596182224,
"light.light99": 596247761,
}
await aid_storage.async_save()
await hass.async_block_till_done()
aid_storage = AccessoryAidStorage(hass)
await aid_storage.async_initialize()
assert aid_storage.allocations == {
"light.light0": 514851983,
"light.light1": 514917520,
"light.light10": 594609344,
"light.light100": 677446896,
"light.light101": 677512433,
"light.light102": 677577970,
"light.light103": 677643507,
"light.light104": 677709044,
"light.light105": 677774581,
"light.light106": 677840118,
"light.light107": 677905655,
"light.light108": 677971192,
"light.light109": 678036729,
"light.light11": 594674881,
"light.light110": 677577969,
"light.light111": 677643506,
"light.light112": 677709043,
"light.light113": 677774580,
"light.light114": 677840117,
"light.light115": 677905654,
"light.light116": 677971191,
"light.light117": 678036728,
"light.light118": 678102265,
"light.light119": 678167802,
"light.light12": 594740418,
"light.light120": 677709042,
"light.light121": 677774579,
"light.light122": 677840116,
"light.light123": 677905653,
"light.light124": 677971190,
"light.light125": 678036727,
"light.light126": 678102264,
"light.light127": 678167801,
"light.light128": 678233338,
"light.light129": 678298875,
"light.light13": 594805955,
"light.light130": 677840115,
"light.light131": 677905652,
"light.light132": 677971189,
"light.light133": 678036726,
"light.light134": 678102263,
"light.light135": 678167800,
"light.light136": 678233337,
"light.light137": 678298874,
"light.light138": 678364411,
"light.light139": 678429948,
"light.light14": 594871492,
"light.light140": 677971188,
"light.light141": 678036725,
"light.light142": 678102262,
"light.light143": 678167799,
"light.light144": 678233336,
"light.light145": 678298873,
"light.light146": 678364410,
"light.light147": 678429947,
"light.light148": 678495484,
"light.light149": 678561021,
"light.light15": 594937029,
"light.light150": 678102261,
"light.light151": 678167798,
"light.light152": 678233335,
"light.light153": 678298872,
"light.light154": 678364409,
"light.light155": 678429946,
"light.light156": 678495483,
"light.light157": 678561020,
"light.light158": 678626557,
"light.light159": 678692094,
"light.light16": 595002566,
"light.light160": 678233334,
"light.light161": 678298871,
"light.light162": 678364408,
"light.light163": 678429945,
"light.light164": 678495482,
"light.light165": 678561019,
"light.light166": 678626556,
"light.light167": 678692093,
"light.light168": 678757630,
"light.light169": 678823167,
"light.light17": 595068103,
"light.light170": 678364407,
"light.light171": 678429944,
"light.light172": 678495481,
"light.light173": 678561018,
"light.light174": 678626555,
"light.light175": 678692092,
"light.light176": 678757629,
"light.light177": 678823166,
"light.light178": 678888703,
"light.light179": 678954240,
"light.light18": 595133640,
"light.light180": 678495480,
"light.light181": 678561017,
"light.light182": 678626554,
"light.light183": 678692091,
"light.light184": 678757628,
"light.light185": 678823165,
"light.light186": 678888702,
"light.light187": 678954239,
"light.light188": 679019776,
"light.light189": 679085313,
"light.light19": 595199177,
"light.light190": 678626553,
"light.light191": 678692090,
"light.light192": 678757627,
"light.light193": 678823164,
"light.light194": 678888701,
"light.light195": 678954238,
"light.light196": 679019775,
"light.light197": 679085312,
"light.light198": 679150849,
"light.light199": 679216386,
"light.light2": 514983057,
"light.light20": 594740417,
"light.light200": 677643505,
"light.light201": 1682157970,
"light.light202": 1665380351,
"light.light203": 1648602732,
"light.light204": 1631825113,
"light.light205": 1615047494,
"light.light206": 1598269875,
"light.light207": 1581492256,
"light.light208": 1833156541,
"light.light209": 1816378922,
"light.light21": 594805954,
"light.light210": 677774578,
"light.light211": 1614900399,
"light.light212": 1631678018,
"light.light213": 1648455637,
"light.light214": 1531012304,
"light.light215": 1547789923,
"light.light216": 1564567542,
"light.light217": 1581345161,
"light.light218": 1732343732,
"light.light219": 1749121351,
"light.light22": 594871491,
"light.light23": 594937028,
"light.light24": 595002565,
"light.light25": 595068102,
"light.light26": 595133639,
"light.light27": 595199176,
"light.light28": 595264713,
"light.light29": 595330250,
"light.light3": 515048594,
"light.light30": 594871490,
"light.light31": 594937027,
"light.light32": 595002564,
"light.light33": 595068101,
"light.light34": 595133638,
"light.light35": 595199175,
"light.light36": 595264712,
"light.light37": 595330249,
"light.light38": 595395786,
"light.light39": 595461323,
"light.light4": 515114131,
"light.light40": 595002563,
"light.light41": 595068100,
"light.light42": 595133637,
"light.light43": 595199174,
"light.light44": 595264711,
"light.light45": 595330248,
"light.light46": 595395785,
"light.light47": 595461322,
"light.light48": 595526859,
"light.light49": 595592396,
"light.light5": 515179668,
"light.light50": 595133636,
"light.light51": 595199173,
"light.light52": 595264710,
"light.light53": 595330247,
"light.light54": 595395784,
"light.light55": 595461321,
"light.light56": 595526858,
"light.light57": 595592395,
"light.light58": 595657932,
"light.light59": 595723469,
"light.light6": 515245205,
"light.light60": 595264709,
"light.light61": 595330246,
"light.light62": 595395783,
"light.light63": 595461320,
"light.light64": 595526857,
"light.light65": 595592394,
"light.light66": 595657931,
"light.light67": 595723468,
"light.light68": 595789005,
"light.light69": 595854542,
"light.light7": 515310742,
"light.light70": 595395782,
"light.light71": 595461319,
"light.light72": 595526856,
"light.light73": 595592393,
"light.light74": 595657930,
"light.light75": 595723467,
"light.light76": 595789004,
"light.light77": 595854541,
"light.light78": 595920078,
"light.light79": 595985615,
"light.light8": 515376279,
"light.light80": 595526855,
"light.light81": 595592392,
"light.light82": 595657929,
"light.light83": 595723466,
"light.light84": 595789003,
"light.light85": 595854540,
"light.light86": 595920077,
"light.light87": 595985614,
"light.light88": 596051151,
"light.light89": 596116688,
"light.light9": 515441816,
"light.light90": 595657928,
"light.light91": 595723465,
"light.light92": 595789002,
"light.light93": 595854539,
"light.light94": 595920076,
"light.light95": 595985613,
"light.light96": 596051150,
"light.light97": 596116687,
"light.light98": 596182224,
"light.light99": 596247761,
}
aid_storage_path = hass.config.path(STORAGE_DIR, AID_MANAGER_STORAGE_KEY)
if await hass.async_add_executor_job(os.path.exists, aid_storage_path):
await hass.async_add_executor_job(os.unlink, aid_storage_path)