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:
- Drop frames whose
status is AUTH_FAILURE, UNPROCESSED or DROP_FRAME, and forward the rest as now. Small and closes the hole.
- 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.
Follow-up to #713, deliberately left out of it to keep that PR to one subject.
_handle_gp_framein #713 unpacks thegpepIncomingMessageHandlercallback and discards four of its fields:status,autoCommissioning,bidirectionalInfoandmic. It then forwards the GPD command payload as a ZCL GP Notification.statusis the one that matters.sl_GpStatus(bellows/types/named.py) distinguishes: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,autoCommissioningandbidirectionalInfothat zigpy can never recover.Two options, roughly in increasing order of effort:
statusisAUTH_FAILURE,UNPROCESSEDorDROP_FRAME, and forward the rest as now. Small and closes the hole.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.