From 27650455ef4295ce0f0d96919ef0a026f9f7f411 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:01:27 +0000 Subject: [PATCH 1/8] Initial plan From af5aa39484dbffd3e2f5276a5e277cdee0b6bef1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:09:59 +0000 Subject: [PATCH 2/8] Change LOGGER.warning to LOGGER.debug in security.py Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- zigpy_znp/znp/security.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zigpy_znp/znp/security.py b/zigpy_znp/znp/security.py index acc8ce5..fcbcf11 100644 --- a/zigpy_znp/znp/security.py +++ b/zigpy_znp/znp/security.py @@ -296,7 +296,7 @@ async def read_devices( async for key in read_hashed_link_keys(znp, tclk_seed): if key.partner_ieee not in devices: - LOGGER.warning( + LOGGER.debug( "Skipping hashed link key %s (tx: %s, rx: %s) for unknown device %s", ":".join(f"{b:02x}" for b in key.key), key.tx_counter, @@ -309,7 +309,7 @@ async def read_devices( async for key in read_unhashed_link_keys(znp, addr_mgr): if key.partner_ieee not in devices: - LOGGER.warning( + LOGGER.debug( "Skipping unhashed link key %s (tx: %s, rx: %s) for unknown device %s", ":".join(f"{b:02x}" for b in key.key), key.tx_counter, From 794e2a6e6ebd99c8d75675742ac3becea3f54b45 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:19:40 +0000 Subject: [PATCH 3/8] Remove PRE_COMMIT_CACHE_PATH and PYTHON_VERSION_DEFAULT from CI config Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f9c584..015997f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,6 @@ jobs: with: CODE_FOLDER: zigpy_znp CACHE_VERSION: 2 - PRE_COMMIT_CACHE_PATH: ~/.cache/pre-commit - PYTHON_VERSION_DEFAULT: 3.9.15 MINIMUM_COVERAGE_PERCENTAGE: 95 secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 1cdd7a5bc874149daaca5b90eec2f5f284612fa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:31:32 +0000 Subject: [PATCH 4/8] Replace serial_asyncio with serial_asyncio_fast in unit tests Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- tests/conftest.py | 2 +- tests/test_uart.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 059981b..0a15190 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -154,7 +154,7 @@ def passthrough_serial_conn(loop, protocol_factory, url, *args, **kwargs): return fut mocker.patch( - "serial_asyncio.create_serial_connection", new=passthrough_serial_conn + "serial_asyncio_fast.create_serial_connection", new=passthrough_serial_conn ) # So we don't have to import it every time diff --git a/tests/test_uart.py b/tests/test_uart.py index 80efbb1..d16c5af 100644 --- a/tests/test_uart.py +++ b/tests/test_uart.py @@ -1,7 +1,7 @@ import asyncio import pytest -from serial_asyncio import SerialTransport +from serial_asyncio_fast import SerialTransport import zigpy_znp.types as t import zigpy_znp.config as conf @@ -47,7 +47,7 @@ def create_serial_conn(loop, protocol_factory, url, *args, **kwargs): return fut - mocker.patch("serial_asyncio.create_serial_connection", new=create_serial_conn) + mocker.patch("serial_asyncio_fast.create_serial_connection", new=create_serial_conn) return device, serial_interface From 4dc374b6738bab601480b62d04c68397002135d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 13:49:26 +0000 Subject: [PATCH 5/8] Update concurrency from 2 to 4 and use max_concurrency instead of max_value Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- tests/application/test_startup.py | 4 ++-- zigpy_znp/zigbee/application.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/application/test_startup.py b/tests/application/test_startup.py index d3098ff..54b551b 100644 --- a/tests/application/test_startup.py +++ b/tests/application/test_startup.py @@ -304,7 +304,7 @@ async def test_reset_network_info(device, make_application): "device, concurrency", [ (FormedLaunchpadCC26X2R1, 16), - (FormedZStack1CC2531, 2), + (FormedZStack1CC2531, 4), ], ) async def test_concurrency_auto_config(device, concurrency, make_application): @@ -312,4 +312,4 @@ async def test_concurrency_auto_config(device, concurrency, make_application): await app.connect() await app.start_network() - assert app._concurrent_requests_semaphore.max_value == concurrency + assert app._concurrent_requests_semaphore.max_concurrency == concurrency diff --git a/zigpy_znp/zigbee/application.py b/zigpy_znp/zigbee/application.py index 06984a3..a9e2ce1 100644 --- a/zigpy_znp/zigbee/application.py +++ b/zigpy_znp/zigbee/application.py @@ -187,12 +187,12 @@ async def start_network(self, *, read_only=False): None, zigpy.config.defaults.CONF_MAX_CONCURRENT_REQUESTS_DEFAULT, ): - max_concurrent_requests = 16 if self._znp.nvram.align_structs else 2 + max_concurrent_requests = 16 if self._znp.nvram.align_structs else 4 else: max_concurrent_requests = self._config[conf.CONF_MAX_CONCURRENT_REQUESTS] # Update the max value of the concurrent request semaphore at runtime - self._concurrent_requests_semaphore.max_value = max_concurrent_requests + self._concurrent_requests_semaphore.max_concurrency = max_concurrent_requests if self.state.network_info.network_key.key == const.Z2M_NETWORK_KEY: LOGGER.warning( From 8c1e6496378ebc507acd4fb1a7455dc12dc4b300 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:39:45 +0000 Subject: [PATCH 6/8] Fix locked() call to pass priority parameter Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- tests/application/test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/application/test_requests.py b/tests/application/test_requests.py index 4c7e4dc..2c49361 100644 --- a/tests/application/test_requests.py +++ b/tests/application/test_requests.py @@ -294,7 +294,7 @@ async def callback(req): nonlocal in_flight_requests nonlocal did_lock - if app._concurrent_requests_semaphore.locked(): + if app._concurrent_requests_semaphore.locked(priority=0): did_lock = True in_flight_requests += 1 From 00dc971468692bd26023fbba4aebbeffdf3d1c50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:57:49 +0000 Subject: [PATCH 7/8] Add test coverage for debug logging in security.py Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- tests/tools/test_security_logging.py | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/tools/test_security_logging.py diff --git a/tests/tools/test_security_logging.py b/tests/tools/test_security_logging.py new file mode 100644 index 0000000..16466e4 --- /dev/null +++ b/tests/tools/test_security_logging.py @@ -0,0 +1,97 @@ +"""Test coverage for security.py logging statements.""" +import logging + + +import zigpy_znp.types as t +from zigpy_znp.znp import security +from zigpy_znp.types.nvids import ExNvIds, OsalNvIds + +from ..conftest import BaseZStack3CC2531 + + +async def test_read_devices_skips_unknown_link_keys(make_connected_znp, caplog): + """Test that read_devices logs debug messages for link keys without matching devices. + + This test covers the debug logging statements in read_devices(): + - "Skipping hashed link key ... for unknown device" + - "Skipping unhashed link key ... for unknown device" + + These occur when link keys exist in NVRAM but don't have corresponding + entries in the address manager table. + """ + znp, znp_server = await make_connected_znp(BaseZStack3CC2531) + + # Initialize LEGACY section if not exists + if ExNvIds.LEGACY not in znp_server._nvram: + znp_server._nvram[ExNvIds.LEGACY] = {} + + # Set up IEEEs for test + known_ieee = t.EUI64.convert("11:22:33:44:55:66:77:88") + unknown_hashed_ieee = t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11") + + # Set up address manager with only the known device - must be serialized + addr_mgr_table = t.AddressManagerTable([ + t.AddrMgrEntry( + type=t.AddrMgrUserType.Security, + nwkAddr=t.NWK(0x1234), + extAddr=known_ieee, + ), + # Fill with empty entries + *[t.AddrMgrEntry( + type=t.AddrMgrUserType.Default, + nwkAddr=t.NWK(0xFFFF), + extAddr=t.EUI64.convert("00:00:00:00:00:00:00:00"), + ) for _ in range(15)], + ]) + znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.ADDRMGR] = addr_mgr_table.serialize() + + # Add a hashed link key for an unknown device (not in address manager) + znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.LEGACY_TCLK_TABLE_START] = ( + t.TCLKDevEntry( + extAddr=unknown_hashed_ieee, + txFrmCntr=100, + rxFrmCntr=200, + keyAttributes=t.KeyAttributes.VERIFIED_KEY, + keyType=t.KeyType.NONE, + SeedShift_IcIndex=0, + ).serialize() + ) + + # Add another hashed link key for a second unknown device to verify logging + znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.LEGACY_TCLK_TABLE_START + 1] = ( + t.TCLKDevEntry( + extAddr=t.EUI64.convert("bb:bb:bb:bb:bb:bb:bb:bb"), + txFrmCntr=150, + rxFrmCntr=250, + keyAttributes=t.KeyAttributes.VERIFIED_KEY, + keyType=t.KeyType.NONE, + SeedShift_IcIndex=1, + ).serialize() + ) + + # For ZStack 3.0, also need APS link key table (even if empty) + znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\x00" * 16 + + tclk_seed = t.KeyData(b"\xAA" * 16) + + # Capture debug logs + caplog.set_level(logging.DEBUG, logger="zigpy_znp.znp.security") + + # Call read_devices - should log debug messages for unknown devices + devices = await security.read_devices(znp, tclk_seed=tclk_seed) + + # Verify that debug messages were logged for hashed keys + assert "Skipping hashed link key" in caplog.text + assert str(unknown_hashed_ieee) in caplog.text + + # Verify messages are at DEBUG level + debug_records = [r for r in caplog.records if "Skipping" in r.message and "link key" in r.message] + assert len(debug_records) >= 2 # At least 2 unknown devices + assert all(r.levelname == "DEBUG" for r in debug_records) + + # Verify that only the known device is returned (no orphan keys) + assert len(devices) == 1 + assert devices[0].node_info.ieee == known_ieee + + # For ZStack 3.0, also need APS link key table (even if empty) + znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\x00" * 16 From b1a8d9cd2507f27d5e72c2dae4118674048c1849 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:02:05 +0000 Subject: [PATCH 8/8] Revert "Add test coverage for debug logging in security.py" Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> --- tests/tools/test_security_logging.py | 97 ---------------------------- 1 file changed, 97 deletions(-) delete mode 100644 tests/tools/test_security_logging.py diff --git a/tests/tools/test_security_logging.py b/tests/tools/test_security_logging.py deleted file mode 100644 index 16466e4..0000000 --- a/tests/tools/test_security_logging.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Test coverage for security.py logging statements.""" -import logging - - -import zigpy_znp.types as t -from zigpy_znp.znp import security -from zigpy_znp.types.nvids import ExNvIds, OsalNvIds - -from ..conftest import BaseZStack3CC2531 - - -async def test_read_devices_skips_unknown_link_keys(make_connected_znp, caplog): - """Test that read_devices logs debug messages for link keys without matching devices. - - This test covers the debug logging statements in read_devices(): - - "Skipping hashed link key ... for unknown device" - - "Skipping unhashed link key ... for unknown device" - - These occur when link keys exist in NVRAM but don't have corresponding - entries in the address manager table. - """ - znp, znp_server = await make_connected_znp(BaseZStack3CC2531) - - # Initialize LEGACY section if not exists - if ExNvIds.LEGACY not in znp_server._nvram: - znp_server._nvram[ExNvIds.LEGACY] = {} - - # Set up IEEEs for test - known_ieee = t.EUI64.convert("11:22:33:44:55:66:77:88") - unknown_hashed_ieee = t.EUI64.convert("aa:bb:cc:dd:ee:ff:00:11") - - # Set up address manager with only the known device - must be serialized - addr_mgr_table = t.AddressManagerTable([ - t.AddrMgrEntry( - type=t.AddrMgrUserType.Security, - nwkAddr=t.NWK(0x1234), - extAddr=known_ieee, - ), - # Fill with empty entries - *[t.AddrMgrEntry( - type=t.AddrMgrUserType.Default, - nwkAddr=t.NWK(0xFFFF), - extAddr=t.EUI64.convert("00:00:00:00:00:00:00:00"), - ) for _ in range(15)], - ]) - znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.ADDRMGR] = addr_mgr_table.serialize() - - # Add a hashed link key for an unknown device (not in address manager) - znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.LEGACY_TCLK_TABLE_START] = ( - t.TCLKDevEntry( - extAddr=unknown_hashed_ieee, - txFrmCntr=100, - rxFrmCntr=200, - keyAttributes=t.KeyAttributes.VERIFIED_KEY, - keyType=t.KeyType.NONE, - SeedShift_IcIndex=0, - ).serialize() - ) - - # Add another hashed link key for a second unknown device to verify logging - znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.LEGACY_TCLK_TABLE_START + 1] = ( - t.TCLKDevEntry( - extAddr=t.EUI64.convert("bb:bb:bb:bb:bb:bb:bb:bb"), - txFrmCntr=150, - rxFrmCntr=250, - keyAttributes=t.KeyAttributes.VERIFIED_KEY, - keyType=t.KeyType.NONE, - SeedShift_IcIndex=1, - ).serialize() - ) - - # For ZStack 3.0, also need APS link key table (even if empty) - znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\x00" * 16 - - tclk_seed = t.KeyData(b"\xAA" * 16) - - # Capture debug logs - caplog.set_level(logging.DEBUG, logger="zigpy_znp.znp.security") - - # Call read_devices - should log debug messages for unknown devices - devices = await security.read_devices(znp, tclk_seed=tclk_seed) - - # Verify that debug messages were logged for hashed keys - assert "Skipping hashed link key" in caplog.text - assert str(unknown_hashed_ieee) in caplog.text - - # Verify messages are at DEBUG level - debug_records = [r for r in caplog.records if "Skipping" in r.message and "link key" in r.message] - assert len(debug_records) >= 2 # At least 2 unknown devices - assert all(r.levelname == "DEBUG" for r in debug_records) - - # Verify that only the known device is returned (no orphan keys) - assert len(devices) == 1 - assert devices[0].node_info.ieee == known_ieee - - # For ZStack 3.0, also need APS link key table (even if empty) - znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.APS_LINK_KEY_TABLE] = b"\x00" * 16