Bump python-otbr-api to 2.1.0 (#93790)

* Bump python-otbr-api to 2.1.0

* Fix tests
This commit is contained in:
Erik Montnemery 2023-05-30 12:47:46 +02:00 committed by GitHub
parent 4596ff0ce5
commit 16d8c8d4d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 41 additions and 51 deletions

View file

@ -3,10 +3,12 @@ from __future__ import annotations
import asyncio import asyncio
import logging import logging
from typing import cast
import aiohttp import aiohttp
import python_otbr_api import python_otbr_api
from python_otbr_api import tlv_parser from python_otbr_api import tlv_parser
from python_otbr_api.tlv_parser import MeshcopTLVType
import voluptuous as vol import voluptuous as vol
import yarl import yarl
@ -38,8 +40,8 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
thread_dataset_tlv = await async_get_preferred_dataset(self.hass) thread_dataset_tlv = await async_get_preferred_dataset(self.hass)
if thread_dataset_tlv: if thread_dataset_tlv:
dataset = tlv_parser.parse_tlv(thread_dataset_tlv) dataset = tlv_parser.parse_tlv(thread_dataset_tlv)
if channel_str := dataset.get(tlv_parser.MeshcopTLVType.CHANNEL): if channel := dataset.get(MeshcopTLVType.CHANNEL):
thread_dataset_channel = int(channel_str, base=16) thread_dataset_channel = cast(tlv_parser.Channel, channel).channel
if thread_dataset_tlv is not None and ( if thread_dataset_tlv is not None and (
not allowed_channel or allowed_channel == thread_dataset_channel not allowed_channel or allowed_channel == thread_dataset_channel

View file

@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/otbr", "documentation": "https://www.home-assistant.io/integrations/otbr",
"integration_type": "service", "integration_type": "service",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["python-otbr-api==2.0.0"] "requirements": ["python-otbr-api==2.1.0"]
} }

View file

@ -5,11 +5,12 @@ from collections.abc import Callable, Coroutine
import contextlib import contextlib
import dataclasses import dataclasses
from functools import wraps from functools import wraps
from typing import Any, Concatenate, ParamSpec, TypeVar from typing import Any, Concatenate, ParamSpec, TypeVar, cast
import python_otbr_api import python_otbr_api
from python_otbr_api import tlv_parser from python_otbr_api import tlv_parser
from python_otbr_api.pskc import compute_pskc from python_otbr_api.pskc import compute_pskc
from python_otbr_api.tlv_parser import MeshcopTLVType
from homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon import ( from homeassistant.components.homeassistant_hardware.silabs_multiprotocol_addon import (
is_multiprotocol_url, is_multiprotocol_url,
@ -146,14 +147,10 @@ async def _warn_on_channel_collision(
dataset = tlv_parser.parse_tlv(dataset_tlvs.hex()) dataset = tlv_parser.parse_tlv(dataset_tlvs.hex())
if (channel_s := dataset.get(tlv_parser.MeshcopTLVType.CHANNEL)) is None: if (channel_s := dataset.get(MeshcopTLVType.CHANNEL)) is None:
delete_issue()
return
try:
channel = int(channel_s, 16)
except ValueError:
delete_issue() delete_issue()
return return
channel = cast(tlv_parser.Channel, channel_s).channel
if channel == allowed_channel: if channel == allowed_channel:
delete_issue() delete_issue()
@ -186,20 +183,20 @@ def _warn_on_default_network_settings(
insecure = False insecure = False
if ( if (
network_key := dataset.get(tlv_parser.MeshcopTLVType.NETWORKKEY) network_key := dataset.get(MeshcopTLVType.NETWORKKEY)
) is not None and bytes.fromhex(network_key) in INSECURE_NETWORK_KEYS: ) is not None and network_key.data in INSECURE_NETWORK_KEYS:
insecure = True insecure = True
if ( if (
not insecure not insecure
and tlv_parser.MeshcopTLVType.EXTPANID in dataset and MeshcopTLVType.EXTPANID in dataset
and tlv_parser.MeshcopTLVType.NETWORKNAME in dataset and MeshcopTLVType.NETWORKNAME in dataset
and tlv_parser.MeshcopTLVType.PSKC in dataset and MeshcopTLVType.PSKC in dataset
): ):
ext_pan_id = dataset[tlv_parser.MeshcopTLVType.EXTPANID] ext_pan_id = dataset[MeshcopTLVType.EXTPANID]
network_name = dataset[tlv_parser.MeshcopTLVType.NETWORKNAME] network_name = cast(tlv_parser.NetworkName, dataset[MeshcopTLVType.NETWORKNAME])
pskc = bytes.fromhex(dataset[tlv_parser.MeshcopTLVType.PSKC]) pskc = dataset[MeshcopTLVType.PSKC].data
for passphrase in INSECURE_PASSPHRASES: for passphrase in INSECURE_PASSPHRASES:
if pskc == compute_pskc(ext_pan_id, network_name, passphrase): if pskc == compute_pskc(ext_pan_id.data, network_name.name, passphrase):
insecure = True insecure = True
break break

View file

@ -1,7 +1,10 @@
"""Websocket API for OTBR.""" """Websocket API for OTBR."""
from typing import cast
import python_otbr_api import python_otbr_api
from python_otbr_api import tlv_parser from python_otbr_api import tlv_parser
from python_otbr_api.tlv_parser import MeshcopTLVType
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
@ -133,8 +136,8 @@ async def websocket_set_network(
connection.send_error(msg["id"], "unknown_dataset", "Unknown dataset") connection.send_error(msg["id"], "unknown_dataset", "Unknown dataset")
return return
dataset = tlv_parser.parse_tlv(dataset_tlv) dataset = tlv_parser.parse_tlv(dataset_tlv)
if channel_str := dataset.get(tlv_parser.MeshcopTLVType.CHANNEL): if channel := dataset.get(MeshcopTLVType.CHANNEL):
thread_dataset_channel = int(channel_str, base=16) thread_dataset_channel = cast(tlv_parser.Channel, channel).channel
data: OTBRData = hass.data[DOMAIN] data: OTBRData = hass.data[DOMAIN]
allowed_channel = await get_allowed_channel(hass, data.url) allowed_channel = await get_allowed_channel(hass, data.url)

View file

@ -1,13 +1,13 @@
"""Persistently store thread datasets.""" """Persistently store thread datasets."""
from __future__ import annotations from __future__ import annotations
from contextlib import suppress
import dataclasses import dataclasses
from datetime import datetime from datetime import datetime
from functools import cached_property from functools import cached_property
from typing import Any, cast from typing import Any, cast
from python_otbr_api import tlv_parser from python_otbr_api import tlv_parser
from python_otbr_api.tlv_parser import MeshcopTLVType
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -39,31 +39,33 @@ class DatasetEntry:
@property @property
def channel(self) -> int | None: def channel(self) -> int | None:
"""Return channel as an integer.""" """Return channel as an integer."""
if (channel := self.dataset.get(tlv_parser.MeshcopTLVType.CHANNEL)) is None: if (channel := self.dataset.get(MeshcopTLVType.CHANNEL)) is None:
return None return None
with suppress(ValueError): return cast(tlv_parser.Channel, channel).channel
return int(channel, 16)
return None
@cached_property @cached_property
def dataset(self) -> dict[tlv_parser.MeshcopTLVType, str]: def dataset(self) -> dict[MeshcopTLVType, tlv_parser.MeshcopTLVItem]:
"""Return the dataset in dict format.""" """Return the dataset in dict format."""
return tlv_parser.parse_tlv(self.tlv) return tlv_parser.parse_tlv(self.tlv)
@property @property
def extended_pan_id(self) -> str | None: def extended_pan_id(self) -> str | None:
"""Return extended PAN ID as a hex string.""" """Return extended PAN ID as a hex string."""
return self.dataset.get(tlv_parser.MeshcopTLVType.EXTPANID) if (ext_pan_id := self.dataset.get(MeshcopTLVType.EXTPANID)) is None:
return None
return str(ext_pan_id)
@property @property
def network_name(self) -> str | None: def network_name(self) -> str | None:
"""Return network name as a string.""" """Return network name as a string."""
return self.dataset.get(tlv_parser.MeshcopTLVType.NETWORKNAME) if (name := self.dataset.get(MeshcopTLVType.NETWORKNAME)) is None:
return None
return cast(tlv_parser.NetworkName, name).name
@property @property
def pan_id(self) -> str | None: def pan_id(self) -> str | None:
"""Return PAN ID as a hex string.""" """Return PAN ID as a hex string."""
return self.dataset.get(tlv_parser.MeshcopTLVType.PANID) return str(self.dataset.get(MeshcopTLVType.PANID))
def to_json(self) -> dict[str, Any]: def to_json(self) -> dict[str, Any]:
"""Return a JSON serializable representation for storage.""" """Return a JSON serializable representation for storage."""

View file

@ -148,7 +148,8 @@ async def async_get_config_entry_diagnostics(
"unexpected_routers": set(), "unexpected_routers": set(),
}, },
) )
if mlp := record.dataset.get(MeshcopTLVType.MESHLOCALPREFIX): if mlp_item := record.dataset.get(MeshcopTLVType.MESHLOCALPREFIX):
mlp = str(mlp_item)
network["prefixes"].add(f"{mlp[0:4]}:{mlp[4:8]}:{mlp[8:12]}:{mlp[12:16]}") network["prefixes"].add(f"{mlp[0:4]}:{mlp[4:8]}:{mlp[8:12]}:{mlp[12:16]}")
# Find all routes currently act that might be thread related, so we can match them to # Find all routes currently act that might be thread related, so we can match them to

View file

@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/thread", "documentation": "https://www.home-assistant.io/integrations/thread",
"integration_type": "service", "integration_type": "service",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["python-otbr-api==2.0.0", "pyroute2==0.7.5"], "requirements": ["python-otbr-api==2.1.0", "pyroute2==0.7.5"],
"zeroconf": ["_meshcop._udp.local."] "zeroconf": ["_meshcop._udp.local."]
} }

View file

@ -2109,7 +2109,7 @@ python-opensky==0.0.7
# homeassistant.components.otbr # homeassistant.components.otbr
# homeassistant.components.thread # homeassistant.components.thread
python-otbr-api==2.0.0 python-otbr-api==2.1.0
# homeassistant.components.picnic # homeassistant.components.picnic
python-picnic-api==1.1.0 python-picnic-api==1.1.0

View file

@ -1532,7 +1532,7 @@ python-nest==4.2.0
# homeassistant.components.otbr # homeassistant.components.otbr
# homeassistant.components.thread # homeassistant.components.thread
python-otbr-api==2.0.0 python-otbr-api==2.1.0
# homeassistant.components.picnic # homeassistant.components.picnic
python-picnic-api==1.1.0 python-picnic-api==1.1.0

View file

@ -24,12 +24,6 @@ from . import (
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.test_util.aiohttp import AiohttpClientMocker from tests.test_util.aiohttp import AiohttpClientMocker
DATASET_BAD_CHANNEL = bytes.fromhex(
"0E080000000000010000000035060004001FFFE00208F642646DA209B1C00708FDF57B5A"
"0FE2AAF60510DE98B5BA1A528FEE049D4B4B01835375030D4F70656E5468726561642048410102"
"25A40410F5DD18371BFD29E1A601EF6FFAD94C030C0402A0F7F8"
)
DATASET_NO_CHANNEL = bytes.fromhex( DATASET_NO_CHANNEL = bytes.fromhex(
"0E08000000000001000035060004001FFFE00208F642646DA209B1C00708FDF57B5A" "0E08000000000001000035060004001FFFE00208F642646DA209B1C00708FDF57B5A"
"0FE2AAF60510DE98B5BA1A528FEE049D4B4B01835375030D4F70656E5468726561642048410102" "0FE2AAF60510DE98B5BA1A528FEE049D4B4B01835375030D4F70656E5468726561642048410102"
@ -103,9 +97,7 @@ async def test_import_share_radio_channel_collision(hass: HomeAssistant) -> None
) )
@pytest.mark.parametrize( @pytest.mark.parametrize("dataset", [DATASET_CH15, DATASET_NO_CHANNEL])
"dataset", [DATASET_BAD_CHANNEL, DATASET_CH15, DATASET_NO_CHANNEL]
)
async def test_import_share_radio_no_channel_collision( async def test_import_share_radio_no_channel_collision(
hass: HomeAssistant, dataset: bytes hass: HomeAssistant, dataset: bytes
) -> None: ) -> None:

View file

@ -121,7 +121,6 @@ async def test_dataset_properties(hass: HomeAssistant) -> None:
{"source": "Google", "tlv": DATASET_1}, {"source": "Google", "tlv": DATASET_1},
{"source": "Multipan", "tlv": DATASET_2}, {"source": "Multipan", "tlv": DATASET_2},
{"source": "🎅", "tlv": DATASET_3}, {"source": "🎅", "tlv": DATASET_3},
{"source": "test1", "tlv": DATASET_1_BAD_CHANNEL},
{"source": "test2", "tlv": DATASET_1_NO_CHANNEL}, {"source": "test2", "tlv": DATASET_1_NO_CHANNEL},
] ]
@ -136,10 +135,8 @@ async def test_dataset_properties(hass: HomeAssistant) -> None:
dataset_2 = dataset dataset_2 = dataset
if dataset.source == "🎅": if dataset.source == "🎅":
dataset_3 = dataset dataset_3 = dataset
if dataset.source == "test1":
dataset_4 = dataset
if dataset.source == "test2": if dataset.source == "test2":
dataset_5 = dataset dataset_4 = dataset
dataset = store.async_get(dataset_1.id) dataset = store.async_get(dataset_1.id)
assert dataset == dataset_1 assert dataset == dataset_1
@ -166,10 +163,6 @@ async def test_dataset_properties(hass: HomeAssistant) -> None:
assert dataset == dataset_4 assert dataset == dataset_4
assert dataset.channel is None assert dataset.channel is None
dataset = store.async_get(dataset_5.id)
assert dataset == dataset_5
assert dataset.channel is None
async def test_load_datasets(hass: HomeAssistant) -> None: async def test_load_datasets(hass: HomeAssistant) -> None:
"""Make sure that we can load/save data correctly.""" """Make sure that we can load/save data correctly."""