From fd116fc40803f0787fc1a4d4d2aea86814dee5d3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 24 Nov 2021 12:57:13 -0600 Subject: [PATCH] Refactor zeroconf matching to be more DRY (#60293) --- homeassistant/components/zeroconf/__init__.py | 80 +++++++++---------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index dd42b2146d7..932c2dee377 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -48,6 +48,18 @@ HOMEKIT_TYPES = [ "_hap._udp.local.", ] +# Keys we support matching against in properties that are always matched in +# upper case. ex: ZeroconfServiceInfo.properties["macaddress"] +UPPER_MATCH_PROPS = {"macaddress"} +# Keys we support matching against in properties that are always matched in +# lower case. ex: ZeroconfServiceInfo.properties["model"] +LOWER_MATCH_PROPS = {"manufacturer", "model"} +# Top level keys we support matching against in properties that are always matched in +# lower case. ex: ZeroconfServiceInfo.name +LOWER_MATCH_ATTRS = {"name"} +# Everything we support matching +ALL_MATCHERS = UPPER_MATCH_PROPS | LOWER_MATCH_PROPS | LOWER_MATCH_ATTRS + CONF_DEFAULT_INTERFACE = "default_interface" CONF_IPV6 = "ipv6" DEFAULT_DEFAULT_INTERFACE = True @@ -306,6 +318,18 @@ async def _async_register_hass_zc_service( await aio_zc.async_register_service(info, allow_name_change=True) +def _match_against_data(matcher: dict[str, str], match_data: dict[str, str]) -> bool: + """Check a matcher to ensure all values in match_data match.""" + return not any( + key + for key in ALL_MATCHERS + if key in matcher + and ( + key not in match_data or not fnmatch.fnmatch(match_data[key], matcher[key]) + ) + ) + + class ZeroconfDiscovery: """Discovery via zeroconf.""" @@ -380,10 +404,10 @@ class ZeroconfDiscovery: return _LOGGER.debug("Discovered new device %s %s", name, info) + props: dict[str, str] = info.properties # If we can handle it as a HomeKit discovery, we do that here. if service_type in HOMEKIT_TYPES: - props = info.properties if domain := async_get_homekit_discovery_domain(self.homekit_models, props): discovery_flow.async_create_flow( self.hass, domain, {"source": config_entries.SOURCE_HOMEKIT}, info @@ -405,52 +429,22 @@ class ZeroconfDiscovery: # likely bad homekit data return - if info.name: - lowercase_name: str | None = info.name.lower() - else: - lowercase_name = None - - if "macaddress" in info.properties: - uppercase_mac: str | None = info.properties["macaddress"].upper() - else: - uppercase_mac = None - - if "manufacturer" in info.properties: - lowercase_manufacturer: str | None = info.properties["manufacturer"].lower() - else: - lowercase_manufacturer = None - - if "model" in info.properties: - lowercase_model: str | None = info.properties["model"].lower() - else: - lowercase_model = None + match_data: dict[str, str] = {} + for key in LOWER_MATCH_ATTRS: + attr_value: str = getattr(info, key) + match_data[key] = attr_value.lower() + for key in UPPER_MATCH_PROPS: + if key in props: + match_data[key] = props[key].upper() + for key in LOWER_MATCH_PROPS: + if key in props: + match_data[key] = props[key].lower() # Not all homekit types are currently used for discovery # so not all service type exist in zeroconf_types for matcher in self.zeroconf_types.get(service_type, []): - if len(matcher) > 1: - if "macaddress" in matcher and ( - uppercase_mac is None - or not fnmatch.fnmatch(uppercase_mac, matcher["macaddress"]) - ): - continue - if "name" in matcher and ( - lowercase_name is None - or not fnmatch.fnmatch(lowercase_name, matcher["name"]) - ): - continue - if "manufacturer" in matcher and ( - lowercase_manufacturer is None - or not fnmatch.fnmatch( - lowercase_manufacturer, matcher["manufacturer"] - ) - ): - continue - if "model" in matcher and ( - lowercase_model is None - or not fnmatch.fnmatch(lowercase_model, matcher["model"]) - ): - continue + if len(matcher) > 1 and not _match_against_data(matcher, match_data): + continue discovery_flow.async_create_flow( self.hass,