diff --git a/homeassistant/components/thread/discovery.py b/homeassistant/components/thread/discovery.py index 1006a44d5d3..d07469f36fb 100644 --- a/homeassistant/components/thread/discovery.py +++ b/homeassistant/components/thread/discovery.py @@ -57,25 +57,29 @@ def async_discovery_data_from_service( except UnicodeDecodeError: return None - ext_addr = service.properties.get(b"xa") - ext_pan_id = service.properties.get(b"xp") - network_name = try_decode(service.properties.get(b"nn")) - model_name = try_decode(service.properties.get(b"mn")) + # Service properties are always bytes if they are set from the network. + # For legacy backwards compatibility zeroconf allows properties to be set + # as strings but we never do that so we can safely cast here. + service_properties = cast(dict[bytes, bytes | None], service.properties) + ext_addr = service_properties.get(b"xa") + ext_pan_id = service_properties.get(b"xp") + network_name = try_decode(service_properties.get(b"nn")) + model_name = try_decode(service_properties.get(b"mn")) server = service.server - vendor_name = try_decode(service.properties.get(b"vn")) - thread_version = try_decode(service.properties.get(b"tv")) + vendor_name = try_decode(service_properties.get(b"vn")) + thread_version = try_decode(service_properties.get(b"tv")) unconfigured = None brand = KNOWN_BRANDS.get(vendor_name) if brand == "homeassistant": # Attempt to detect incomplete configuration - if (state_bitmap_b := service.properties.get(b"sb")) is not None: + if (state_bitmap_b := service_properties.get(b"sb")) is not None: try: state_bitmap = StateBitmap.from_bytes(state_bitmap_b) if not state_bitmap.is_active: unconfigured = True except ValueError: _LOGGER.debug("Failed to decode state bitmap in service %s", service) - if service.properties.get(b"at") is None: + if service_properties.get(b"at") is None: unconfigured = True return ThreadRouterDiscoveryData( @@ -168,10 +172,19 @@ class ThreadRouterDiscovery: return _LOGGER.debug("_add_update_service %s %s", name, service) + # Service properties are always bytes if they are set from the network. + # For legacy backwards compatibility zeroconf allows properties to be set + # as strings but we never do that so we can safely cast here. + service_properties = cast(dict[bytes, bytes | None], service.properties) + + if not (xa := service_properties.get(b"xa")): + _LOGGER.debug("_add_update_service failed to find xa in %s", service) + return + # We use the extended mac address as key, bail out if it's missing try: - extended_mac_address = service.properties[b"xa"].hex() - except (KeyError, UnicodeDecodeError) as err: + extended_mac_address = xa.hex() + except UnicodeDecodeError as err: _LOGGER.debug("_add_update_service failed to parse service %s", err) return diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index f77909b1bdd..b85f9f0fd83 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -553,11 +553,17 @@ def info_from_service(service: AsyncServiceInfo) -> ZeroconfServiceInfo | None: break if not host: return None + + # Service properties are always bytes if they are set from the network. + # For legacy backwards compatibility zeroconf allows properties to be set + # as strings but we never do that so we can safely cast here. + service_properties = cast(dict[bytes, bytes | None], service.properties) + properties: dict[str, Any] = { k.decode("ascii", "replace"): None if v is None else v.decode("utf-8", "replace") - for k, v in service.properties.items() + for k, v in service_properties.items() } assert service.server is not None, "server cannot be none if there are addresses" diff --git a/homeassistant/components/zeroconf/manifest.json b/homeassistant/components/zeroconf/manifest.json index bb0ec29271e..cd7b9e95e75 100644 --- a/homeassistant/components/zeroconf/manifest.json +++ b/homeassistant/components/zeroconf/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_push", "loggers": ["zeroconf"], "quality_scale": "internal", - "requirements": ["zeroconf==0.72.3"] + "requirements": ["zeroconf==0.74.0"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 12e8afc3cf7..fa5b52478d0 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -52,7 +52,7 @@ voluptuous-serialize==2.6.0 voluptuous==0.13.1 webrtcvad==2.0.10 yarl==1.9.2 -zeroconf==0.72.3 +zeroconf==0.74.0 # Constrain pycryptodome to avoid vulnerability # see https://github.com/home-assistant/core/pull/16238 diff --git a/requirements_all.txt b/requirements_all.txt index a682ca35542..f5476b8647d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2752,7 +2752,7 @@ zamg==0.2.4 zengge==0.2 # homeassistant.components.zeroconf -zeroconf==0.72.3 +zeroconf==0.74.0 # homeassistant.components.zeversolar zeversolar==0.3.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e1d2fdbc0d4..e77638d777e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2025,7 +2025,7 @@ youtubeaio==1.1.5 zamg==0.2.4 # homeassistant.components.zeroconf -zeroconf==0.72.3 +zeroconf==0.74.0 # homeassistant.components.zeversolar zeversolar==0.3.1