Skip to content

Commit 924083f

Browse files
committed
Replace internal network state objects with zigpy.state
1 parent b1ded73 commit 924083f

File tree

6 files changed

+163
-139
lines changed

6 files changed

+163
-139
lines changed

tests/tools/test_network_backup_restore.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import json
2+
import dataclasses
23

34
import pytest
5+
import zigpy.state
46
from jsonschema import ValidationError
57

68
import zigpy_znp.types as t
79
import zigpy_znp.config as conf
810
from zigpy_znp.api import ZNP
911
from zigpy_znp.znp import security
10-
from zigpy_znp.znp.utils import NetworkInfo
1112
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
1213
from zigpy_znp.tools.common import validate_backup_json
1314
from zigpy_znp.zigbee.application import ControllerApplication
@@ -26,17 +27,26 @@
2627
)
2728
from ..application.test_startup import DEV_NETWORK_SETTINGS
2829

29-
BARE_NETWORK_INFO = NetworkInfo(
30+
BARE_NETWORK_INFO = zigpy.state.NetworkInformation(
3031
extended_pan_id=t.EUI64.convert("ab:de:fa:bc:de:fa:bc:de"),
31-
ieee=None,
32-
nwk=None,
3332
channel=None,
34-
channels=None,
33+
channel_mask=None,
3534
pan_id=None,
3635
nwk_update_id=None,
3736
security_level=None,
38-
network_key=None,
39-
network_key_seq=None,
37+
network_key=zigpy.state.Key(
38+
key=None,
39+
seq=None,
40+
tx_counter=None,
41+
rx_counter=None,
42+
partner_ieee=None,
43+
),
44+
)
45+
46+
BARE_NODE_INFO = zigpy.state.NodeInfo(
47+
ieee=None,
48+
nwk=None,
49+
logical_type=None,
4050
)
4151

4252

@@ -225,9 +235,12 @@ async def mock_startup(self, *, force_form):
225235
ControllerApplication, "startup", side_effect=mock_startup, autospec=True
226236
)
227237

228-
load_nwk_info_mock = mocker.patch(
229-
"zigpy_znp.api.load_network_info",
230-
new=CoroutineMock(return_value=BARE_NETWORK_INFO),
238+
def mock_load_network_info(self, *args, **kwargs):
239+
self.network_info = BARE_NETWORK_INFO
240+
self.node_info = BARE_NODE_INFO
241+
242+
load_nwk_info_mock = mocker.patch.object(
243+
ZNP, "load_network_info", side_effect=mock_load_network_info, autospec=True
231244
)
232245

233246
write_tc_counter_mock = mocker.patch(
@@ -312,6 +325,7 @@ async def test_tc_frame_counter_zstack1(make_connected_znp):
312325
async def test_tc_frame_counter_zstack30(make_connected_znp):
313326
znp, znp_server = await make_connected_znp(BaseZStack3CC2531)
314327
znp.network_info = BARE_NETWORK_INFO
328+
znp.node_info = BARE_NODE_INFO
315329
znp_server._nvram[ExNvIds.LEGACY] = {
316330
# This value is ignored
317331
OsalNvIds.NWKKEY: b"\x01" + b"\xAB" * 16 + b"\x78\x56\x34\x12",
@@ -330,8 +344,8 @@ async def test_tc_frame_counter_zstack30(make_connected_znp):
330344
assert (await security.read_tc_frame_counter(znp)) == 0x00000001
331345

332346
# If we change the EPID, the generic entry will be used
333-
znp.network_info = znp.network_info.replace(
334-
extended_pan_id=t.EUI64.convert("11:22:33:44:55:66:77:88")
347+
znp.network_info = dataclasses.replace(
348+
znp.network_info, extended_pan_id=t.EUI64.convert("11:22:33:44:55:66:77:88")
335349
)
336350
assert (await security.read_tc_frame_counter(znp)) == 0x00000002
337351

@@ -343,6 +357,7 @@ async def test_tc_frame_counter_zstack30(make_connected_znp):
343357
async def test_tc_frame_counter_zstack33(make_connected_znp):
344358
znp, znp_server = await make_connected_znp(BaseLaunchpadCC26X2R1)
345359
znp.network_info = BARE_NETWORK_INFO
360+
znp.node_info = BARE_NODE_INFO
346361
znp_server._nvram = {
347362
ExNvIds.LEGACY: {
348363
# This value is ignored
@@ -362,8 +377,8 @@ async def test_tc_frame_counter_zstack33(make_connected_znp):
362377
assert (await security.read_tc_frame_counter(znp)) == 0x00000002
363378

364379
# If we change the EPID, the generic entry will be used. It doesn't exist.
365-
znp.network_info = znp.network_info.replace(
366-
extended_pan_id=t.EUI64.convert("11:22:33:44:55:66:77:88")
380+
znp.network_info = dataclasses.replace(
381+
znp.network_info, extended_pan_id=t.EUI64.convert("11:22:33:44:55:66:77:88")
367382
)
368383

369384
with pytest.raises(ValueError):

zigpy_znp/api.py

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import contextlib
88
from collections import Counter, defaultdict
99

10+
import zigpy.state
1011
import async_timeout
12+
import zigpy.zdo.types as zdo_t
1113

1214
import zigpy_znp.types as t
1315
import zigpy_znp.config as conf
@@ -21,8 +23,8 @@
2123
CallbackResponseListener,
2224
)
2325
from zigpy_znp.frames import GeneralFrame
24-
from zigpy_znp.znp.utils import NetworkInfo, load_network_info, detect_zstack_version
2526
from zigpy_znp.exceptions import CommandNotRecognized, InvalidCommandResponse
27+
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
2628

2729
LOGGER = logging.getLogger(__name__)
2830
AFTER_CONNECT_DELAY = 1 # seconds
@@ -42,14 +44,126 @@ def __init__(self, config: conf.ConfigType):
4244
self.version = None
4345

4446
self.nvram = NVRAMHelper(self)
45-
self.network_info: NetworkInfo = None
47+
self.network_info: zigpy.state.NetworkInformation = None
48+
self.node_info: zigpy.state.NodeInfo = None
4649

4750
def set_application(self, app):
4851
assert self._app is None
4952
self._app = app
5053

51-
async def load_network_info(self):
52-
self.network_info = await load_network_info(self)
54+
async def detect_zstack_version(self) -> float:
55+
"""
56+
Feature detects the major version of Z-Stack running on the device.
57+
"""
58+
59+
# Z-Stack 1.2 does not have the AppConfig subsystem
60+
if not self.capabilities & t.MTCapabilities.APP_CNF:
61+
return 1.2
62+
63+
try:
64+
# Only Z-Stack 3.30+ has the new NVRAM system
65+
await self.nvram.read(
66+
item_id=ExNvIds.TCLK_TABLE,
67+
sub_id=0x0000,
68+
item_type=t.Bytes,
69+
)
70+
return 3.30
71+
except KeyError:
72+
return 3.30
73+
except CommandNotRecognized:
74+
return 3.0
75+
76+
async def load_network_info(self, *, load_keys=False):
77+
"""
78+
Loads low-level network information from NVRAM.
79+
Loading key data greatly increases the runtime so it not enabled by default.
80+
"""
81+
82+
from zigpy_znp.znp import security
83+
84+
is_on_network = None
85+
nib = None
86+
87+
try:
88+
nib = await self.nvram.osal_read(OsalNvIds.NIB, item_type=t.NIB)
89+
is_on_network = nib.nwkLogicalChannel != 0 and nib.nwkKeyLoaded
90+
except KeyError:
91+
is_on_network = False
92+
else:
93+
if is_on_network and self.version >= 3.0:
94+
# This NVRAM item is the very first thing initialized in `zgInit`
95+
is_on_network = (
96+
await self.nvram.osal_read(
97+
OsalNvIds.BDBNODEISONANETWORK, item_type=t.uint8_t
98+
)
99+
== 1
100+
)
101+
102+
if not is_on_network:
103+
raise ValueError("Device is not a part of a network")
104+
105+
key_desc = await self.nvram.osal_read(
106+
OsalNvIds.NWK_ACTIVE_KEY_INFO, item_type=t.NwkKeyDesc
107+
)
108+
109+
tclk_seed = None
110+
111+
if self.version > 1.2:
112+
tclk_seed = await self.nvram.osal_read(
113+
OsalNvIds.TCLK_SEED, item_type=t.KeyData
114+
)
115+
116+
tc_frame_counter = await security.read_tc_frame_counter(
117+
self, ext_pan_id=nib.extendedPANID
118+
)
119+
120+
network_info = zigpy.state.NetworkInformation(
121+
extended_pan_id=nib.extendedPANID,
122+
pan_id=nib.nwkPanId,
123+
nwk_update_id=nib.nwkUpdateId,
124+
channel=nib.nwkLogicalChannel,
125+
channel_mask=nib.channelList,
126+
security_level=nib.SecurityLevel,
127+
network_key=zigpy.state.Key(
128+
key=key_desc.Key,
129+
seq=key_desc.KeySeqNum,
130+
tx_counter=tc_frame_counter,
131+
rx_counter=None,
132+
partner_ieee=None,
133+
),
134+
tc_link_key=None,
135+
key_table=[],
136+
stack_specific={
137+
"TCLK_SEED": tclk_seed,
138+
},
139+
)
140+
141+
# This takes a few seconds
142+
if load_keys:
143+
for device in await security.read_devices(self):
144+
network_info.key_table.append(
145+
zigpy.state.Key(
146+
key=device.aps_link_key,
147+
seq=0,
148+
tx_counter=device.tx_counter,
149+
rx_counter=device.rx_counter,
150+
partner_ieee=device.ieee,
151+
)
152+
)
153+
154+
ieee = await self.nvram.osal_read(OsalNvIds.EXTADDR, item_type=t.EUI64)
155+
logical_type = await self.nvram.osal_read(
156+
OsalNvIds.LOGICAL_TYPE, item_type=t.DeviceLogicalType
157+
)
158+
159+
node_info = zigpy.state.NodeInfo(
160+
ieee=ieee,
161+
nwk=nib.nwkDevAddress,
162+
logical_type=zdo_t.LogicalType(logical_type),
163+
)
164+
165+
self.network_info = network_info
166+
self.node_info = node_info
53167

54168
async def reset(self) -> None:
55169
"""
@@ -117,7 +231,7 @@ async def connect(self, *, test_port=True) -> None:
117231

118232
# We need to know how structs are packed to deserialize frames corectly
119233
await self.nvram.determine_alignment()
120-
self.version = await detect_zstack_version(self)
234+
self.version = await self.detect_zstack_version()
121235

122236
LOGGER.debug("Detected Z-Stack %s", self.version)
123237
except (Exception, asyncio.CancelledError):

zigpy_znp/tools/network_backup.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
import zigpy_znp
1010
import zigpy_znp.types as t
1111
from zigpy_znp.api import ZNP
12-
from zigpy_znp.types.nvids import OsalNvIds
1312
from zigpy_znp.tools.common import ClosableFileType, setup_parser, validate_backup_json
14-
from zigpy_znp.znp.security import read_devices, read_tc_frame_counter
13+
from zigpy_znp.znp.security import read_devices
1514
from zigpy_znp.zigbee.application import ControllerApplication
1615

1716
LOGGER = logging.getLogger(__name__)
@@ -56,26 +55,23 @@ async def backup_network(znp: ZNP) -> t.JSONType:
5655
},
5756
},
5857
},
59-
"coordinator_ieee": znp.network_info.ieee.serialize()[::-1].hex(),
58+
"coordinator_ieee": znp.node_info.ieee.serialize()[::-1].hex(),
6059
"pan_id": znp.network_info.pan_id.serialize()[::-1].hex(),
6160
"extended_pan_id": znp.network_info.extended_pan_id.serialize()[::-1].hex(),
6261
"nwk_update_id": znp.network_info.nwk_update_id,
6362
"security_level": znp.network_info.security_level,
6463
"channel": znp.network_info.channel,
65-
"channel_mask": list(znp.network_info.channels),
64+
"channel_mask": list(znp.network_info.channel_mask),
6665
"network_key": {
67-
"key": znp.network_info.network_key.serialize().hex(),
68-
"sequence_number": znp.network_info.network_key_seq,
69-
"frame_counter": await read_tc_frame_counter(znp),
66+
"key": znp.network_info.network_key.key.serialize().hex(),
67+
"sequence_number": znp.network_info.network_key.seq,
68+
"frame_counter": znp.network_info.network_key.tx_counter,
7069
},
7170
"devices": devices,
7271
}
7372

74-
if znp.version > 1.2:
75-
tclk_seed = await znp.nvram.osal_read(OsalNvIds.TCLK_SEED, item_type=t.Bytes)
76-
LOGGER.info("TCLK seed: %s", tclk_seed.hex())
77-
78-
obj["stack_specific"] = {"zstack": {"tclk_seed": tclk_seed.hex()}}
73+
if znp.network_info.stack_specific:
74+
obj["stack_specific"] = znp.network_info.stack_specific
7975

8076
# Ensure our generated backup is valid
8177
validate_backup_json(obj)

zigpy_znp/zigbee/application.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,22 +1282,8 @@ async def _load_network_info(self) -> None:
12821282

12831283
await self._znp.load_network_info()
12841284

1285-
self.ieee = self._znp.network_info.ieee
1286-
self.nwk = self._znp.network_info.nwk
1287-
self.state.network_information.channel = self._znp.network_info.channel
1288-
self.state.network_information.channel_mask = self._znp.network_info.channels
1289-
self.state.network_information.pan_id = self._znp.network_info.pan_id
1290-
self.state.network_information.extended_pan_id = (
1291-
self._znp.network_info.extended_pan_id
1292-
)
1293-
self.state.network_information.nwk_update_id = (
1294-
self._znp.network_info.nwk_update_id
1295-
)
1296-
nwk_key = zigpy.state.Key(
1297-
key=self._znp.network_info.network_key,
1298-
seq=self._znp.network_info.network_key_seq,
1299-
)
1300-
self.state.network_information.network_key = nwk_key
1285+
self.state.node_information = self._znp.node_info
1286+
self.state.network_information = self._znp.network_info
13011287

13021288
def _find_endpoint(self, dst_ep: int, profile: int, cluster: int) -> int:
13031289
"""

zigpy_znp/znp/security.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ def iter_seed_candidates(
7070
yield count, tclk_seed
7171

7272

73-
async def read_tc_frame_counter(znp: ZNP) -> t.uint32_t:
73+
async def read_tc_frame_counter(znp: ZNP, *, ext_pan_id: t.EUI64 = None) -> t.uint32_t:
74+
if ext_pan_id is None and znp.network_info is not None:
75+
ext_pan_id = znp.network_info.extended_pan_id
76+
7477
if znp.version == 1.2:
7578
key_info = await znp.nvram.osal_read(
7679
OsalNvIds.NWKKEY, item_type=t.NwkActiveKeyItems
@@ -93,7 +96,7 @@ async def read_tc_frame_counter(znp: ZNP) -> t.uint32_t:
9396
)
9497

9598
async for entry in entries:
96-
if entry.ExtendedPanID == znp.network_info.extended_pan_id:
99+
if entry.ExtendedPanID == ext_pan_id:
97100
# Always prefer the entry for our current network
98101
return entry.FrameCounter
99102
elif entry.ExtendedPanID == t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"):

0 commit comments

Comments
 (0)