Skip to content

Commit 2e2d1a1

Browse files
committed
Handle new formation corner cases and use zigpy.state more
1 parent 97e4c24 commit 2e2d1a1

File tree

10 files changed

+327
-530
lines changed

10 files changed

+327
-530
lines changed

tests/application/test_update_network.py

Lines changed: 0 additions & 57 deletions
This file was deleted.

tests/conftest.py

Lines changed: 24 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import zigpy.endpoint
1717
import zigpy.zdo.types as zdo_t
1818

19+
import zigpy_znp.const as const
1920
import zigpy_znp.types as t
2021
import zigpy_znp.config as conf
2122
import zigpy_znp.commands as c
@@ -404,16 +405,11 @@ def _create_network_nvram(self):
404405
FrameCounter=0,
405406
)
406407

407-
self._nvram[ExNvIds.LEGACY][OsalNvIds.STARTUP_OPTION] = self.nvram_serialize(
408-
t.StartupOptions.NONE
409-
)
410-
self._nvram[ExNvIds.LEGACY][OsalNvIds.NWKKEY] = self.nvram_serialize(empty_key)
411-
self._nvram[ExNvIds.LEGACY][
412-
OsalNvIds.NWK_ACTIVE_KEY_INFO
413-
] = self.nvram_serialize(empty_key.Active)
414-
self._nvram[ExNvIds.LEGACY][
415-
OsalNvIds.NWK_ALTERN_KEY_INFO
416-
] = self.nvram_serialize(empty_key.Active)
408+
legacy = self._nvram[ExNvIds.LEGACY]
409+
legacy[OsalNvIds.STARTUP_OPTION] = self.nvram_serialize(t.StartupOptions.NONE)
410+
legacy[OsalNvIds.NWKKEY] = self.nvram_serialize(empty_key)
411+
legacy[OsalNvIds.NWK_ACTIVE_KEY_INFO] = self.nvram_serialize(empty_key.Active)
412+
legacy[OsalNvIds.NWK_ALTERN_KEY_INFO] = self.nvram_serialize(empty_key.Active)
417413

418414
def update_device_state(self, state):
419415
self.device_state = state
@@ -426,7 +422,7 @@ def create_nib(self, _=None):
426422
nib.channelList, _ = t.Channels.deserialize(
427423
self._nvram[ExNvIds.LEGACY][OsalNvIds.CHANLIST]
428424
)
429-
nib.nwkLogicalChannel = list(nib.channelList)[0]
425+
nib.nwkLogicalChannel = (list(nib.channelList) + [11])[0]
430426

431427
if OsalNvIds.APS_USE_EXT_PANID in self._nvram[ExNvIds.LEGACY]:
432428
epid = self._nvram[ExNvIds.LEGACY][OsalNvIds.APS_USE_EXT_PANID]
@@ -459,61 +455,7 @@ def create_nib(self, _=None):
459455
self._nvram[ExNvIds.LEGACY][OsalNvIds.NWKKEY] = self.nvram_serialize(key_info)
460456

461457
def _default_nib(self):
462-
return t.NIB(
463-
SequenceNum=0,
464-
PassiveAckTimeout=5,
465-
MaxBroadcastRetries=2,
466-
MaxChildren=0,
467-
MaxDepth=20,
468-
MaxRouters=0,
469-
dummyNeighborTable=0,
470-
BroadcastDeliveryTime=30,
471-
ReportConstantCost=0,
472-
RouteDiscRetries=0,
473-
dummyRoutingTable=0,
474-
SecureAllFrames=1,
475-
SecurityLevel=5,
476-
SymLink=1,
477-
CapabilityFlags=143,
478-
TransactionPersistenceTime=7,
479-
nwkProtocolVersion=2,
480-
RouteDiscoveryTime=5,
481-
RouteExpiryTime=30,
482-
nwkDevAddress=0xFFFE,
483-
nwkLogicalChannel=0,
484-
nwkCoordAddress=0xFFFE,
485-
nwkCoordExtAddress=t.EUI64.convert("00:00:00:00:00:00:00:00"),
486-
nwkPanId=0xFFFF,
487-
nwkState=t.NwkState.NWK_INIT,
488-
channelList=t.Channels.NO_CHANNELS,
489-
beaconOrder=15,
490-
superFrameOrder=15,
491-
scanDuration=0,
492-
battLifeExt=0,
493-
allocatedRouterAddresses=0,
494-
allocatedEndDeviceAddresses=0,
495-
nodeDepth=0,
496-
extendedPANID=t.EUI64.convert("00:00:00:00:00:00:00:00"),
497-
nwkKeyLoaded=t.Bool.false,
498-
spare1=t.NwkKeyDesc(
499-
KeySeqNum=0, Key=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
500-
),
501-
spare2=t.NwkKeyDesc(
502-
KeySeqNum=0, Key=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
503-
),
504-
spare3=0,
505-
spare4=0,
506-
nwkLinkStatusPeriod=60,
507-
nwkRouterAgeLimit=3,
508-
nwkUseMultiCast=t.Bool.false,
509-
nwkIsConcentrator=t.Bool.true,
510-
nwkConcentratorDiscoveryTime=120,
511-
nwkConcentratorRadius=10,
512-
nwkAllFresh=1,
513-
nwkManagerAddr=0x0000,
514-
nwkTotalTransmissions=0,
515-
nwkUpdateId=0,
516-
)
458+
return const.DEFAULT_NIB.replace()
517459

518460
@reply_to(c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000))
519461
def active_endpoints_request(self, req):
@@ -898,7 +840,7 @@ def handle_bdb_start_commissioning(self, request):
898840
RemainingModes=c.app_config.BDBCommissioningMode.NwkFormation,
899841
),
900842
c.AppConfig.BDBCommissioningNotification.Callback(
901-
Status=c.app_config.BDBCommissioningStatus.FormationFailure,
843+
Status=c.app_config.BDBCommissioningStatus.Success,
902844
Mode=c.app_config.BDBCommissioningMode.NwkFormation,
903845
RemainingModes=c.app_config.BDBCommissioningMode.NONE,
904846
),
@@ -957,6 +899,14 @@ class BaseLaunchpadCC26X2R1(BaseZStack3Device):
957899
align_structs = True
958900
code_revision = 20200805
959901

902+
def _create_network_nvram(self):
903+
super()._create_network_nvram()
904+
self._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\xFF" * 20
905+
self._nvram[ExNvIds.ADDRMGR] = {
906+
addr: self.nvram_serialize(const.EMPTY_ADDR_MGR_ENTRY)
907+
for addr in range(0x0000, 0x0100 + 1)
908+
}
909+
960910
def create_nib(self, _=None):
961911
super().create_nib()
962912

@@ -1065,6 +1015,13 @@ class BaseZStack3CC2531(BaseZStack3Device):
10651015
align_structs = False
10661016
code_revision = 20190425
10671017

1018+
def _create_network_nvram(self):
1019+
super()._create_network_nvram()
1020+
self._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\xFF" * 17
1021+
self._nvram[ExNvIds.LEGACY][OsalNvIds.ADDRMGR] = 124 * self.nvram_serialize(
1022+
const.EMPTY_ADDR_MGR_ENTRY
1023+
)
1024+
10681025
def create_nib(self, _=None):
10691026
super().create_nib()
10701027

@@ -1105,21 +1062,6 @@ def version_replier(self, request):
11051062
def led_responder(self, req):
11061063
return req.Rsp(Status=t.Status.SUCCESS)
11071064

1108-
@reply_to(
1109-
c.AppConfig.BDBStartCommissioning.Req(
1110-
Mode=c.app_config.BDBCommissioningMode.NwkFormation
1111-
)
1112-
)
1113-
def handle_bdb_start_commissioning(self, request):
1114-
result = super().handle_bdb_start_commissioning(request)
1115-
1116-
# This item is only created after a network is formed
1117-
self._nvram[ExNvIds.LEGACY][OsalNvIds.ADDRMGR] = 124 * self.nvram_serialize(
1118-
t.EMPTY_ADDR_MGR_ENTRY
1119-
)
1120-
1121-
return result
1122-
11231065

11241066
class FormedLaunchpadCC26X2R1(BaseLaunchpadCC26X2R1):
11251067
def __init__(self, *args, **kwargs):

tests/tools/test_network_backup_restore.py

Lines changed: 20 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@
66
from jsonschema import ValidationError
77

88
import zigpy_znp.types as t
9-
import zigpy_znp.config as conf
10-
from zigpy_znp.api import ZNP
119
from zigpy_znp.znp import security
1210
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
1311
from zigpy_znp.tools.common import validate_backup_json
14-
from zigpy_znp.zigbee.application import ControllerApplication
1512
from zigpy_znp.tools.network_backup import main as network_backup
1613
from zigpy_znp.tools.network_restore import main as network_restore
1714

1815
from ..conftest import (
1916
ALL_DEVICES,
2017
EMPTY_DEVICES,
2118
FORMED_DEVICES,
22-
CoroutineMock,
2319
BaseZStack1CC2531,
2420
BaseZStack3CC2531,
2521
FormedZStack1CC2531,
@@ -177,135 +173,41 @@ async def test_network_backup_formed(device, make_znp_server, tmp_path):
177173
assert len(backup["devices"]) > 1
178174

179175

180-
@pytest.mark.parametrize("device", FORMED_DEVICES)
181-
@pytest.mark.asyncio
182-
async def test_network_restore_full(
183-
device, make_znp_server, backup_json, tmp_path, mocker
184-
):
185-
backup_file = tmp_path / "backup.json"
186-
backup_file.write_text(json.dumps(backup_json))
187-
188-
znp_server = make_znp_server(server_cls=device)
189-
190-
# Perform the "restore"
191-
await network_restore([znp_server._port_path, "-i", str(backup_file), "-c", "2500"])
192-
193-
194176
@pytest.mark.parametrize("device", ALL_DEVICES)
195177
@pytest.mark.asyncio
196-
async def test_network_restore(device, make_znp_server, backup_json, tmp_path, mocker):
178+
async def test_network_restore_and_backup(
179+
device, make_znp_server, backup_json, tmp_path
180+
):
197181
backup_file = tmp_path / "backup.json"
198182
backup_file.write_text(json.dumps(backup_json))
199183

200184
znp_server = make_znp_server(server_cls=device)
201185

202-
async def mock_startup(self, *, force_form):
203-
assert force_form
204-
205-
config = self.config[conf.CONF_NWK]
206-
207-
assert config[conf.CONF_NWK_KEY] == t.KeyData(
208-
bytes.fromhex("37668fd64e35e03342e5ef9f35ccf4ab")
209-
)
210-
assert config[conf.CONF_NWK_PAN_ID] == 0xFEED
211-
assert config[conf.CONF_NWK_CHANNEL] == 25
212-
assert config[conf.CONF_NWK_EXTENDED_PAN_ID] == t.EUI64.convert(
213-
"ab:de:fa:bc:de:fa:bc:de"
214-
)
215-
216-
znp = ZNP(self.config)
217-
await znp.connect()
218-
219-
if OsalNvIds.APS_LINK_KEY_TABLE not in znp_server._nvram[ExNvIds.LEGACY]:
220-
znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = (
221-
b"\x00" * 1000
222-
)
223-
224-
if OsalNvIds.NIB not in znp_server._nvram[ExNvIds.LEGACY]:
225-
znp_server._nvram[ExNvIds.LEGACY][
226-
OsalNvIds.NIB
227-
] = znp_server.nvram_serialize(znp_server._default_nib())
228-
229-
self._znp = znp
230-
self._znp.set_application(self)
231-
232-
self._bind_callbacks()
233-
234-
startup_mock = mocker.patch.object(
235-
ControllerApplication, "startup", side_effect=mock_startup, autospec=True
236-
)
237-
238-
async 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
244-
)
245-
246-
write_tc_counter_mock = mocker.patch(
247-
"zigpy_znp.tools.network_restore.write_tc_frame_counter", new=CoroutineMock()
248-
)
249-
write_devices_mock = mocker.patch(
250-
"zigpy_znp.tools.network_restore.write_devices", new=CoroutineMock()
251-
)
252-
253-
# Perform the "restore"
186+
# Restore our backup on top of an existing network (or onto an empty device)
254187
await network_restore([znp_server._port_path, "-i", str(backup_file), "-c", "2500"])
255188

256-
# The NIB should contain correct values
257-
nib = znp_server.nvram_deserialize(
258-
znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.NIB], t.NIB
259-
)
260-
assert nib.channelList == t.Channels.from_channel_list([15, 20, 25])
261-
assert nib.nwkUpdateId == 2
262-
assert nib.SecurityLevel == 5
189+
# And then back that restored device up
190+
backup_file2 = tmp_path / "backup2.json"
191+
await network_backup([znp_server._port_path, "-o", str(backup_file2)])
263192

264-
# And validate that the low-level functions were called appropriately
265-
assert startup_mock.call_count == 1
266-
assert startup_mock.mock_calls[0][2]["force_form"] is True
193+
backup_json2 = json.loads(backup_file2.read_text())
267194

268-
assert load_nwk_info_mock.call_count == 1
195+
# Fix up some inconsequential metadata
196+
backup_json2["metadata"]["internal"] = backup_json["metadata"]["internal"]
197+
backup_json2["metadata"]["source"] = backup_json["metadata"]["source"]
198+
backup_json2["network_key"]["frame_counter"] -= 2500
269199

270-
assert write_tc_counter_mock.call_count == 1
271-
assert write_tc_counter_mock.mock_calls[0][1][1] == 66781 + 2500
200+
if issubclass(device, BaseZStack1CC2531):
201+
del backup_json["stack_specific"]["zstack"]["tclk_seed"]
272202

273-
assert write_devices_mock.call_count == 1
274-
write_devices_call = write_devices_mock.mock_calls[0]
203+
if not backup_json["stack_specific"]["zstack"]:
204+
del backup_json["stack_specific"]
275205

276-
assert write_devices_call[2]["counter_increment"] == 2500
206+
for device in backup_json["devices"]:
207+
if "link_key" in device:
208+
del device["link_key"]
277209

278-
if issubclass(device, BaseZStack1CC2531):
279-
assert write_devices_call[2]["tclk_seed"] is None
280-
else:
281-
assert write_devices_call[2]["tclk_seed"] == bytes.fromhex(
282-
"c04884427c8a1ed7bb8412815ccce7aa"
283-
)
284-
285-
assert sorted(write_devices_call[1][1], key=lambda d: d.nwk) == [
286-
security.StoredDevice(
287-
ieee=t.EUI64.convert("00:0b:57:ff:fe:38:b2:12"),
288-
nwk=0x9672,
289-
aps_link_key=t.KeyData.deserialize(
290-
bytes.fromhex("d2fabcbc83dd15d7a9362a7fa39becaa")
291-
)[0],
292-
rx_counter=123,
293-
tx_counter=456,
294-
),
295-
security.StoredDevice(
296-
ieee=t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11"),
297-
nwk=0xABCD,
298-
aps_link_key=t.KeyData.deserialize(
299-
bytes.fromhex("01234567801234567801234567801234")
300-
)[0],
301-
rx_counter=112233,
302-
tx_counter=445566,
303-
),
304-
security.StoredDevice(
305-
ieee=t.EUI64.convert("00:0b:57:ff:fe:36:b9:a0"),
306-
nwk=0xF319,
307-
),
308-
]
210+
assert backup_json == backup_json2
309211

310212

311213
@pytest.mark.asyncio

0 commit comments

Comments
 (0)