Skip to content

Green Power: honour the EZSP status field instead of discarding it #733

Closed

Description

@nmingam

Follow-up to #713, deliberately left out of it to keep that PR to one subject.

_handle_gp_frame in #713 unpacks the gpepIncomingMessageHandler callback and discards four of its fields: status, autoCommissioning, bidirectionalInfo and mic. It then forwards the GPD command payload as a ZCL GP Notification.

status is the one that matters. sl_GpStatus (bellows/types/named.py) distinguishes:

DROP_FRAME       = 0x02
UNPROCESSED      = 0x03
PASS_UNPROCESSED = 0x04
NO_SECURITY      = 0x06
AUTH_FAILURE     = 0x07

So the NCP tells us when it declined to security-process a frame, or when security failed outright, and we currently forward it as an ordinary GP Notification regardless.

That matters because of what a GP Notification means. Per A.3.5.2, a proxy does not send one for a frame whose security processing failed - so a sink is entitled to treat a Notification as already security-processed. And the sink cannot check for itself: the GP Notification (Figures 23/24) carries no MIC and no RxAfterTx bit, and those are part of the CCM* associated data (NWK FC || Ext NWK FC || SrcID || frame counter, A.1.5.4.3.3). There is no way for zigpy to reconstruct the header, so it cannot verify anything.

bellows is the only place this can be fixed properly, because at that point it still holds the mic, autoCommissioning and bidirectionalInfo that zigpy can never recover.

Two options, roughly in increasing order of effort:

  1. Drop frames whose status is AUTH_FAILURE, UNPROCESSED or DROP_FRAME, and forward the rest as now. Small and closes the hole.
  2. Re-emit unprocessed frames as a GP Commissioning Notification with security_failed = 1. That schema does carry the MIC and the extra header bits, which is exactly the route the spec provides for a proxy that could not process security itself. More work, but it is the shape the spec intends and it leaves the sink able to verify.

Option 1 is probably the right first step. Raising it separately rather than growing #713.

Activity

  1. nmingam commented on Jul 23, 2026

    @nmingam
    ContributorAuthor

    Closing this. Looking closer, the framing was wrong.

    zigbee-herdsman's ember adapter forwards every gpepIncomingMessageHandler frame as a plain GP Notification and does not filter on the status field either - it logs the status and forwards. So #713 matches the reference implementation here; discarding status is not the deviation I described.

    The real difference is on the sink side. herdsman's Green Power controller decrypts the payload itself for FullEncr (0b11) frames: CTR-mode AES-128-CCM* with a nonce of srcID/srcID/frameCounter and a zeroed MIC placeholder - it recovers plaintext without authenticating, since a plain Notification carries no MIC to authenticate against. The header is only needed to verify the MIC, not to decrypt.

    That only matters for 0b11 devices. For 0b10 (FullFrameCounterAndMIC) the payload is already cleartext, so forwarding it as-is is correct, and herdsman does not decrypt those either. All the GP hardware captured in these tests is 0b10 (the Busch-Jaeger 6716U), so nothing here exercises the 0b11 path.

    So the open question is not "honour the status field in bellows" but "does the sink need to decrypt 0b11 payloads", which lives in the zigpy manager rather than this forwarding code. It needs a capture from an encrypted GPD to settle whether the NCP hands up ciphertext or plaintext when no sink table is programmed. Tracking it where it belongs rather than here.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions