Skip to content

Fix stale EZSP v14+ command and callback schemas - #750

Open
zigpy-review-bot wants to merge 1 commit into
devfrom
zigpy-bot/v14-stale-schemas
Open

zigpy-review-bot wants to merge 1 commit into
devfrom
zigpy-bot/v14-stale-schemas

Conversation

@zigpy-review-bot

Copy link
Copy Markdown
Collaborator

bellows' EZSP v14 command table copies every v13 command it doesn't override and only swaps EmberStatus/EzspStatus for sl_Status, and v16–v19 inherit that table. Commands whose layout changed in any other way kept their old schemas. bellows never calls any of the commands fixed here, and it only logs leftover bytes at DEBUG level, so nothing visibly broke. But callbacks are parsed whenever the NCP sends them, and several stale entries shared a frame ID with a newer command in the same table. This PR fixes the rest of the outdated definitions from the audit on #747, re-checked against the Silicon Labs sources.

Changes

Requests and responses (v14, SiSDK 2024.6.0):

  • exportLinkKeyByEui, exportTransientKeyByIndex, exportTransientKeyByEui: the status now comes first, followed by the context, the key, and the key metadata.
  • getApsKeyInfo: returns the status, the key metadata, and the context (it used to return the EUI64, the metadata, and the status).
  • importKey: the response now also contains the context.
  • findAndRejoinNetwork: takes two new arguments, reason and nodeType.
  • setAddressTableRemoteEui64 (0x005C) became setAddressTableInfo, which also takes the node ID. It's renamed the same way getAddressTableInfo already was.
  • setPowerDescriptor and clearStoredBeacons: now return an sl_Status.
  • sendPanIdUpdate, readAttribute, writeAttribute: still return a single byte (a bool or a ZCL status), but the global sl_Status replacement had widened it to 4 bytes.

Callbacks (v14):

  • macPassthroughMessageHandler, macFilterMatchMessageHandler, incomingBootloadMessageHandler, zllNetworkFoundHandler, zllAddressAssignmentHandler: lastHopLqi/lastHopRssi were replaced with sl_zigbee_rx_packet_info_t (t.SlRxPacketInfo).
  • rawTransmitCompleteHandler: now carries the message, followed by an sl_Status.

Removed commands (v14):

  • Frame IDs the SDK reused right away: getNextBeacon (0x0004 is now getStoredBeacon), proxyBroadcast (0x0037 is now setNumBeaconsToStore), sendMulticastWithAlias (0x003A is now setupDelayedJoin).
  • Frame IDs the SDK reused later, which collided with commands bellows added for those versions: setAddressTableRemoteNodeId (0x005D) and getAddressTableRemoteNodeId (0x005F) with v17's gpProxyTableRemoveEntry and gpClearProxyTable, incomingSenderEui64Handler (0x0062) with v17's muxInvalidRxHandler, and getFirstBeacon (0x003D) with v18's setPendingNetworkUpdateChannel. Incoming frames already resolved to the newer command, but calling getAddressTableRemoteNodeId() on v17+ would have sent the gpClearProxyTable frame ID.
  • Removed without reuse: sendRawMessage (0x0096; the SDK now uses that name for 0x0051, which bellows calls sendRawMessageExtended), setLongUpTime, setHubConnectivity, isUpTimeLong, isHubConnected, setParentClassificationEnabled, getParentClassificationEnabled.

Later versions:

  • v18 (SiSDK 2025.6.2): getTokenCount returns a uint32_t. The existing v18 macFilterMatchMessageHandler override also gets packetInfo.
  • v19: the token size in getTokenInfo is a uint32_t (new SlZigbeeTokenInfo struct). SiSDK 2025.12 widened it without an EZSP version bump, so v18 firmware sends either width depending on its SDK. v18 is left as-is, with a note.

Older definition (v12/v13):

  • checkKeyContext had been defined with setPassiveAckConfig's arguments since v12. It takes a security manager context and returns an sl_Status in every SDK since v12. It's fixed in v12 and overridden in v13 with the v13 context, like the other security manager commands there.

New tests decode and encode SDK-layout bytes through every affected v14+ table with no bytes left over. They also check that the removed commands are gone and that no v14+ table has duplicate frame IDs. 183 of the 210 new test cases fail on dev.

Not included

Verification
  • EZSP version per SDK, from EZSP_PROTOCOL_VERSION in ezsp-protocol.h: v12 = GSDK 4.3.x, v13 = GSDK 4.4.x, v14 = SiSDK 2024.6.0–2024.6.2, v16 = 2024.6.3 and 2024.12.x, v17 = 2025.6.0–2025.6.1, v18 = 2025.6.2–2025.6.3 and 2025.12.x, v19 = 2026.6.x.
  • Host side: I parsed the encoder and decoder of every command and callback in command-functions.h, expanded the fetch_*/append_* helpers from ezsp-frame-utilities.c/.h, and took frame IDs from ezsp-enum.h. I compared field widths and order against each bellows table. Sources: GSDK 4.3.0 and 4.4.6 and SiSDK 2024.6.0, 2024.12.0, 2025.6.0, 2025.6.2, and 2025.6.3 from GitHub, plus full SiSDK 2025.12.3, 2026.6.0, and 2026.6.1 installs.
  • NCP side: I cross-checked the handlers in app/em260/command-handlers-*-generated.c (2025.12.3) for the security, networking, messaging, and token commands. For launchStandaloneBootloader, and to confirm that extra trailing bytes are ignored, I read processEzspCommand from the 2026.6.1 NCP library's LTO dump.
  • Evidence per change: the v14 layouts are in SiSDK 2024.6.0 command-functions.h and unchanged through 2026.6.1. The frame ID reuse is in ezsp-enum.h (0x0004/0x0037/0x003A in 2024.6.0, 0x005D/0x005F/0x0062 in 2025.6.0, 0x003D in 2025.12.3). getTokenCount is in 2025.6.2 command-functions.h. The getTokenInfo size is fetchInt8u() in 2025.6.3 and fetchInt32u() in 2025.12.3 ezsp-frame-utilities.c. checkKeyContext is in GSDK 4.3.0 and 4.4.6 command-functions.h.
  • With this PR, Fix EZSP v14+ getExtendedTimeout and lookupNodeIdByEui64 response parsing #748, and Fix EZSP v14+ leaveNetwork and setManufacturerCode schemas #749, every command and callback whose layout the SDK changed between v13 and v19 matches bellows' tables. The exceptions are the GP commands, importTransientKey, launchStandaloneBootloader, and v18's getTokenInfo.
  • Review rounds: a Fable 5.1 agent, which re-derived the audit independently, and Copilot (GPT-5.6 Sol) both came back with no material findings.

Related: #747

🤖 Generated with Claude Code

The v14 command table copies every v13 command it doesn't override and only
swaps `EmberStatus`/`EzspStatus` for `sl_Status`. Commands whose layout changed
in any other way at v14 (or later) kept their old schemas, and v16-v19 inherit
them. None of these are called by bellows, so nothing broke visibly, but they
would misparse if used, and callbacks are parsed whenever the NCP sends them.

Checked against the Silicon Labs EZSP host encoders/decoders (GSDK 4.3.0 and
4.4.6, SiSDK 2024.6.0, 2024.12.0, 2025.6.0, 2025.6.2, 2025.12.3, 2026.6.x) and
the NCP handlers where available:

- v14: status moved first or a context was added in the key export/import
  responses (`exportLinkKeyByEui`, `exportTransientKeyByIndex`/`ByEui`,
  `getApsKeyInfo`, `importKey`), `findAndRejoinNetwork` gained `reason` and
  `nodeType`, and `setAddressTableRemoteEui64` became `setAddressTableInfo`.
- v14: `setPowerDescriptor` and `clearStoredBeacons` gained an `sl_Status`
  response, while `sendPanIdUpdate`, `readAttribute`, and `writeAttribute`
  keep their one-byte response that the global replacement had widened.
- v14: callbacks that replaced `lastHopLqi`/`lastHopRssi` with
  `sl_zigbee_rx_packet_info_t`, and `rawTransmitCompleteHandler`, which now
  carries the message.
- v14: drop commands the SDK removed. Several of their frame IDs were reused
  (0x0004, 0x0037, 0x003A right away; 0x003D, 0x005D, 0x005F, 0x0062 in later
  versions, which collided with commands bellows added for v17 and v18).
- v18: `getTokenCount` returns a `uint32_t`, and `macFilterMatchMessageHandler`
  also carries `sl_zigbee_rx_packet_info_t`.
- v19: `getTokenInfo`'s token size is a `uint32_t` (widened in SiSDK 2025.12
  without an EZSP version bump, so v18 is left as-is).
- v12/v13: `checkKeyContext` was defined with `setPassiveAckConfig`'s
  arguments; it takes a security manager context and returns `sl_Status`.
@codecov

codecov Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.55%. Comparing base (8f9d87b) to head (b993b2f).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev     #750   +/-   ##
=======================================
  Coverage   99.55%   99.55%           
=======================================
  Files          64       64           
  Lines        4269     4290   +21     
=======================================
+ Hits         4250     4271   +21     
  Misses         19       19           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant