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 .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__)
@ -85,10 +89,12 @@ class OTBRConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.debug(
"not importing TLV with channel %s", thread_dataset_channel
)
pan_id = generate_random_pan_id()
await api.create_active_dataset(
python_otbr_api.ActiveDataSet(
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)

View file

@ -5,6 +5,7 @@ from collections.abc import Callable, Coroutine
import dataclasses
from functools import wraps
import logging
import random
from typing import Any, Concatenate, ParamSpec, TypeVar, cast
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(
func: 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 .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
@ -99,10 +105,13 @@ async def websocket_create_network(
connection.send_error(msg["id"], "factory_reset_failed", str(exc))
return
pan_id = generate_random_pan_id()
try:
await data.create_active_dataset(
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:

View file

@ -121,9 +121,11 @@ async def test_user_flow_router_not_setup(
# Check we create a dataset and enable the router
assert aioclient_mock.mock_calls[-2][0] == "PUT"
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] == {
"Channel": 15,
"NetworkName": "home-assistant",
"NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
}
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
assert aioclient_mock.mock_calls[-2][0] == "PUT"
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] == {
"Channel": 15,
"NetworkName": "home-assistant",
"NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
}
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
assert aioclient_mock.mock_calls[-2][0] == "PUT"
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] == {
"Channel": 15,
"NetworkName": "home-assistant",
"NetworkName": f"ha-thread-{pan_id:04x}",
"PanId": pan_id,
}
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
) as get_active_dataset_tlvs_mock, patch(
"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"})
msg = await websocket_client.receive_json()
@ -113,7 +116,9 @@ async def test_create_network(
assert msg["result"] is None
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()
assert len(set_enabled_mock.mock_calls) == 2