Randomize thread network names (#108302)

* Randomize thread network names

* Use PAN ID as network name suffix

* Apply suggestions from code review

Co-authored-by: Stefan Agner <stefan@agner.ch>

* Update tests

* Format code

* Change format of network name again

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
This commit is contained in:
Erik Montnemery 2024-01-23 22:58:28 +01:00 committed by GitHub
parent d8f16c14ab
commit 823f268054
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 47 additions and 9 deletions

View file

@ -28,7 +28,11 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DEFAULT_CHANNEL, DOMAIN from .const import DEFAULT_CHANNEL, DOMAIN
from .util import get_allowed_channel from .util import (
compose_default_network_name,
generate_random_pan_id,
get_allowed_channel,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -85,10 +89,12 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.debug( _LOGGER.debug(
"not importing TLV with channel %s", thread_dataset_channel "not importing TLV with channel %s", thread_dataset_channel
) )
pan_id = generate_random_pan_id()
await api.create_active_dataset( await api.create_active_dataset(
python_otbr_api.ActiveDataSet( python_otbr_api.ActiveDataSet(
channel=allowed_channel if allowed_channel else DEFAULT_CHANNEL, channel=allowed_channel if allowed_channel else DEFAULT_CHANNEL,
network_name="home-assistant", network_name=compose_default_network_name(pan_id),
pan_id=pan_id,
) )
) )
await api.set_enabled(True) await api.set_enabled(True)

View file

@ -5,6 +5,7 @@ from collections.abc import Callable, Coroutine
import dataclasses import dataclasses
from functools import wraps from functools import wraps
import logging import logging
import random
from typing import Any, Concatenate, ParamSpec, TypeVar, cast from typing import Any, Concatenate, ParamSpec, TypeVar, cast
import python_otbr_api import python_otbr_api
@ -48,6 +49,17 @@ INSECURE_PASSPHRASES = (
) )
def compose_default_network_name(pan_id: int) -> str:
"""Generate a default network name."""
return f"ha-thread-{pan_id:04x}"
def generate_random_pan_id() -> int:
"""Generate a random PAN ID."""
# PAN ID is 2 bytes, 0xffff is reserved for broadcast
return random.randint(0, 0xFFFE)
def _handle_otbr_error( def _handle_otbr_error(
func: Callable[Concatenate[OTBRData, _P], Coroutine[Any, Any, _R]], func: Callable[Concatenate[OTBRData, _P], Coroutine[Any, Any, _R]],
) -> Callable[Concatenate[OTBRData, _P], Coroutine[Any, Any, _R]]: ) -> Callable[Concatenate[OTBRData, _P], Coroutine[Any, Any, _R]]:

View file

@ -16,7 +16,13 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from .const import DEFAULT_CHANNEL, DOMAIN from .const import DEFAULT_CHANNEL, DOMAIN
from .util import OTBRData, get_allowed_channel, update_issues from .util import (
OTBRData,
compose_default_network_name,
generate_random_pan_id,
get_allowed_channel,
update_issues,
)
@callback @callback
@ -99,10 +105,13 @@ async def websocket_create_network(
connection.send_error(msg["id"], "factory_reset_failed", str(exc)) connection.send_error(msg["id"], "factory_reset_failed", str(exc))
return return
pan_id = generate_random_pan_id()
try: try:
await data.create_active_dataset( await data.create_active_dataset(
python_otbr_api.ActiveDataSet( python_otbr_api.ActiveDataSet(
channel=channel, network_name="home-assistant" channel=channel,
network_name=compose_default_network_name(pan_id),
pan_id=pan_id,
) )
) )
except HomeAssistantError as exc: except HomeAssistantError as exc:

View file

@ -121,9 +121,11 @@ async def test_user_flow_router_not_setup(
# Check we create a dataset and enable the router # Check we create a dataset and enable the router
assert aioclient_mock.mock_calls[-2][0] == "PUT" assert aioclient_mock.mock_calls[-2][0] == "PUT"
assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active" assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active"
pan_id = aioclient_mock.mock_calls[-2][2]["PanId"]
assert aioclient_mock.mock_calls[-2][2] == { assert aioclient_mock.mock_calls[-2][2] == {
"Channel": 15, "Channel": 15,
"NetworkName": "home-assistant", "NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
} }
assert aioclient_mock.mock_calls[-1][0] == "PUT" assert aioclient_mock.mock_calls[-1][0] == "PUT"
@ -425,9 +427,11 @@ async def test_hassio_discovery_flow_router_not_setup(
# Check we create a dataset and enable the router # Check we create a dataset and enable the router
assert aioclient_mock.mock_calls[-2][0] == "PUT" assert aioclient_mock.mock_calls[-2][0] == "PUT"
assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active" assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active"
pan_id = aioclient_mock.mock_calls[-2][2]["PanId"]
assert aioclient_mock.mock_calls[-2][2] == { assert aioclient_mock.mock_calls[-2][2] == {
"Channel": 15, "Channel": 15,
"NetworkName": "home-assistant", "NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
} }
assert aioclient_mock.mock_calls[-1][0] == "PUT" assert aioclient_mock.mock_calls[-1][0] == "PUT"
@ -532,9 +536,11 @@ async def test_hassio_discovery_flow_router_not_setup_has_preferred_2(
# Check we create a dataset and enable the router # Check we create a dataset and enable the router
assert aioclient_mock.mock_calls[-2][0] == "PUT" assert aioclient_mock.mock_calls[-2][0] == "PUT"
assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active" assert aioclient_mock.mock_calls[-2][1].path == "/node/dataset/active"
pan_id = aioclient_mock.mock_calls[-2][2]["PanId"]
assert aioclient_mock.mock_calls[-2][2] == { assert aioclient_mock.mock_calls[-2][2] == {
"Channel": 15, "Channel": 15,
"NetworkName": "home-assistant", "NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
} }
assert aioclient_mock.mock_calls[-1][0] == "PUT" assert aioclient_mock.mock_calls[-1][0] == "PUT"

View file

@ -105,7 +105,10 @@ async def test_create_network(
"python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16 "python_otbr_api.OTBR.get_active_dataset_tlvs", return_value=DATASET_CH16
) as get_active_dataset_tlvs_mock, patch( ) as get_active_dataset_tlvs_mock, patch(
"homeassistant.components.thread.dataset_store.DatasetStore.async_add" "homeassistant.components.thread.dataset_store.DatasetStore.async_add"
) as mock_add: ) as mock_add, patch(
"homeassistant.components.otbr.util.random.randint",
return_value=0x1234,
):
await websocket_client.send_json_auto_id({"type": "otbr/create_network"}) await websocket_client.send_json_auto_id({"type": "otbr/create_network"})
msg = await websocket_client.receive_json() msg = await websocket_client.receive_json()
@ -113,7 +116,9 @@ async def test_create_network(
assert msg["result"] is None assert msg["result"] is None
create_dataset_mock.assert_called_once_with( create_dataset_mock.assert_called_once_with(
python_otbr_api.models.ActiveDataSet(channel=15, network_name="home-assistant") python_otbr_api.models.ActiveDataSet(
channel=15, network_name="ha-thread-1234", pan_id=0x1234
)
) )
factory_reset_mock.assert_called_once_with() factory_reset_mock.assert_called_once_with()
assert len(set_enabled_mock.mock_calls) == 2 assert len(set_enabled_mock.mock_calls) == 2