Skip to content

Commit 8e13e2a

Browse files
abidkhan148claude
andauthored
feat: Implement EZSP v13 SecurityManager API (#1479)
EZSP v13 (EmberZNet 7.4 / Gecko SDK 4.4) removed 12 legacy security commands and replaced them with a new SecurityManager API. PR #1413 bumped EZSP_MAX_VERSION to 13 but did not implement the replacement commands, causing all key management operations to silently fail on EZSP v13 firmware. This commit adds: New data structures: - EzspSecurityManagerKeyType enum - EzspSecurityManagerContext struct - EzspSecurityManagerNetworkKeyInfo struct - EzspSecurityManagerApsKeyMetadata struct New EZSP command classes (11 Request/Response pairs): - exportKey (0x0114) - replaces getKey - importKey (0x0115) - replaces setKeyTableEntry - secManImportTransientKey (0x0111) - replaces addTransientLinkKey - secManExportTransientKeyByEui (0x0113) - replaces getTransientLinkKey - secManExportTransientKeyByIndex (0x0112) - secManExportLinkKeyByEui (0x010D) - secManExportLinkKeyByIndex (0x010F) - secManImportLinkKey (0x010E) - secManCheckKeyContext (0x0110) - secManGetNetworkKeyInfo (0x0116) - secManGetApsKeyInfo (0x010C) Version-conditional routing in EmberNcp: - getKey() detects EZSP v13+ and routes to exportKey - addTransientLinkKey() detects v13+ and routes to secManImportTransientKey Backwards compatible: EZSP v4-v12 paths are unchanged. Fixes #1411 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 577fff9 commit 8e13e2a

30 files changed

Lines changed: 3277 additions & 0 deletions

com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/EmberNcp.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@
2828
import com.zsmartsystems.zigbee.dongle.ember.ezsp.EzspFrame;
2929
import com.zsmartsystems.zigbee.dongle.ember.ezsp.EzspFrameResponse;
3030
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspAddEndpointRequest;
31+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspExportKeyRequest;
32+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspExportKeyResponse;
33+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspSecManImportTransientKeyRequest;
34+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspSecManImportTransientKeyResponse;
35+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspSecManGetNetworkKeyInfoRequest;
36+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspSecManGetNetworkKeyInfoResponse;
37+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspSecurityManagerContext;
38+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspSecurityManagerKeyType;
39+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspSecurityManagerNetworkKeyInfo;
3140
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspAddEndpointResponse;
3241
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspAddOrUpdateKeyTableEntryRequest;
3342
import com.zsmartsystems.zigbee.dongle.ember.ezsp.command.EzspAddOrUpdateKeyTableEntryResponse;
@@ -454,6 +463,35 @@ public EmberStatus clearKeyTable() {
454463
* @return the {@link EmberKeyStruct} or null on error
455464
*/
456465
public EmberKeyStruct getKey(EmberKeyType keyType) {
466+
// EZSP v13+: Use SecurityManager exportKey API (legacy getKey was removed)
467+
if (EzspFrame.getEzspVersion() >= 13) {
468+
EzspSecurityManagerKeyType secManKeyType;
469+
switch (keyType) {
470+
case EMBER_CURRENT_NETWORK_KEY:
471+
secManKeyType = EzspSecurityManagerKeyType.NETWORK;
472+
break;
473+
case EMBER_TRUST_CENTER_LINK_KEY:
474+
secManKeyType = EzspSecurityManagerKeyType.TC_LINK;
475+
break;
476+
case EMBER_APPLICATION_LINK_KEY:
477+
secManKeyType = EzspSecurityManagerKeyType.APP_LINK;
478+
break;
479+
default:
480+
logger.debug("Unsupported key type for EZSP v13 exportKey: {}", keyType);
481+
return null;
482+
}
483+
EmberKeyData keyData = secManExportKey(secManKeyType);
484+
if (keyData == null) {
485+
return null;
486+
}
487+
// Wrap EmberKeyData in an EmberKeyStruct for backwards compatibility
488+
EmberKeyStruct keyStruct = new EmberKeyStruct();
489+
keyStruct.setKey(keyData);
490+
keyStruct.setType(keyType);
491+
return keyStruct;
492+
}
493+
494+
// Legacy EZSP v4-v12
457495
EzspGetKeyRequest request = new EzspGetKeyRequest();
458496
request.setKeyType(keyType);
459497
EzspTransaction transaction = protocolHandler
@@ -470,6 +508,32 @@ public EmberKeyStruct getKey(EmberKeyType keyType) {
470508
return response.getKeyStruct();
471509
}
472510

511+
/**
512+
* Exports a key using the EZSP v13+ SecurityManager API.
513+
*
514+
* @param keyType the {@link EzspSecurityManagerKeyType} to export
515+
* @return the {@link EmberKeyData} or null on error
516+
*/
517+
public EmberKeyData secManExportKey(EzspSecurityManagerKeyType keyType) {
518+
EzspSecurityManagerContext context = new EzspSecurityManagerContext();
519+
context.setKeyType(keyType);
520+
EzspExportKeyRequest request = new EzspExportKeyRequest();
521+
request.setContext(context);
522+
EzspTransaction transaction = protocolHandler
523+
.sendEzspTransaction(new EzspSingleResponseTransaction(request, EzspExportKeyResponse.class));
524+
EzspExportKeyResponse response = (EzspExportKeyResponse) transaction.getResponse();
525+
if (response == null) {
526+
logger.debug("No response from exportKey command");
527+
return null;
528+
}
529+
logger.debug(response.toString());
530+
if (response.getSlStatus() != 0) {
531+
logger.debug("Error exporting key: status=0x{}", String.format("%08X", response.getSlStatus()));
532+
return null;
533+
}
534+
return response.getKey();
535+
}
536+
473537
/**
474538
* Gets a Security Key based on the passed key type.
475539
*
@@ -682,6 +746,28 @@ public int[] getValue(EzspValueId valueId) {
682746
public EmberStatus addTransientLinkKey(IeeeAddress partner, ZigBeeKey transientKey) {
683747
EmberKeyData emberKey = new EmberKeyData();
684748
emberKey.setContents(transientKey.getValue());
749+
750+
// EZSP v13+: Use SecurityManager importTransientKey API
751+
if (EzspFrame.getEzspVersion() >= 13) {
752+
EzspSecManImportTransientKeyRequest request = new EzspSecManImportTransientKeyRequest();
753+
request.setEui64(partner);
754+
request.setPlaintextKey(emberKey);
755+
EzspTransaction transaction = protocolHandler.sendEzspTransaction(
756+
new EzspSingleResponseTransaction(request, EzspSecManImportTransientKeyResponse.class));
757+
EzspSecManImportTransientKeyResponse response = (EzspSecManImportTransientKeyResponse) transaction
758+
.getResponse();
759+
if (response == null) {
760+
return EmberStatus.UNKNOWN;
761+
}
762+
logger.debug(response.toString());
763+
if (response.getSlStatus() != 0) {
764+
logger.debug("Error importing transient key: status=0x{}", String.format("%08X", response.getSlStatus()));
765+
return EmberStatus.EMBER_ERR_FATAL;
766+
}
767+
return EmberStatus.EMBER_SUCCESS;
768+
}
769+
770+
// Legacy EZSP v4-v12
685771
EzspAddTransientLinkKeyRequest request = new EzspAddTransientLinkKeyRequest();
686772
request.setPartner(partner);
687773
request.setTransientKey(emberKey);

com.zsmartsystems.zigbee.dongle.ember/src/main/java/com/zsmartsystems/zigbee/dongle/ember/ezsp/EzspFrame.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public abstract class EzspFrame {
117117
protected static final int FRAME_ID_ENERGY_SCAN_REQUEST = 0x9C;
118118
protected static final int FRAME_ID_ENERGY_SCAN_RESULT_HANDLER = 0x48;
119119
protected static final int FRAME_ID_ERASE_KEY_TABLE_ENTRY = 0x76;
120+
protected static final int FRAME_ID_EXPORT_KEY = 0x0114;
120121
protected static final int FRAME_ID_FIND_AND_REJOIN_NETWORK = 0x21;
121122
protected static final int FRAME_ID_FIND_KEY_TABLE_ENTRY = 0x75;
122123
protected static final int FRAME_ID_FORM_NETWORK = 0x1E;
@@ -177,6 +178,7 @@ public abstract class EzspFrame {
177178
protected static final int FRAME_ID_INCOMING_NETWORK_STATUS_HANDLER = 0xC4;
178179
protected static final int FRAME_ID_INCOMING_ROUTE_ERROR_HANDLER = 0x80;
179180
protected static final int FRAME_ID_INCOMING_ROUTE_RECORD_HANDLER = 0x59;
181+
protected static final int FRAME_ID_IMPORT_KEY = 0x0115;
180182
protected static final int FRAME_ID_INCOMING_SENDER_EUI64_HANDLER = 0x62;
181183
protected static final int FRAME_ID_INVALID_COMMAND = 0x58;
182184
protected static final int FRAME_ID_JOIN_NETWORK = 0x1F;
@@ -221,6 +223,15 @@ public abstract class EzspFrame {
221223
protected static final int FRAME_ID_SEND_REPLY = 0x39;
222224
protected static final int FRAME_ID_SEND_TRUST_CENTER_LINK_KEY = 0x67;
223225
protected static final int FRAME_ID_SEND_UNICAST = 0x34;
226+
protected static final int FRAME_ID_SEC_MAN_CHECK_KEY_CONTEXT = 0x0110;
227+
protected static final int FRAME_ID_SEC_MAN_EXPORT_LINK_KEY_BY_EUI = 0x010D;
228+
protected static final int FRAME_ID_SEC_MAN_EXPORT_LINK_KEY_BY_INDEX = 0x010F;
229+
protected static final int FRAME_ID_SEC_MAN_EXPORT_TRANSIENT_KEY_BY_EUI = 0x0113;
230+
protected static final int FRAME_ID_SEC_MAN_EXPORT_TRANSIENT_KEY_BY_INDEX = 0x0112;
231+
protected static final int FRAME_ID_SEC_MAN_GET_APS_KEY_INFO = 0x010C;
232+
protected static final int FRAME_ID_SEC_MAN_GET_NETWORK_KEY_INFO = 0x0116;
233+
protected static final int FRAME_ID_SEC_MAN_IMPORT_LINK_KEY = 0x010E;
234+
protected static final int FRAME_ID_SEC_MAN_IMPORT_TRANSIENT_KEY = 0x0111;
224235
protected static final int FRAME_ID_SET_ADDRESS_TABLE_REMOTE_EUI64 = 0x5C;
225236
protected static final int FRAME_ID_SET_ADDRESS_TABLE_REMOTE_NODE_ID = 0x5D;
226237
protected static final int FRAME_ID_SET_BINDING = 0x2B;
@@ -288,6 +299,7 @@ public abstract class EzspFrame {
288299
ezspHandlerMap.put(FRAME_ID_ENERGY_SCAN_REQUEST, EzspEnergyScanRequestResponse.class);
289300
ezspHandlerMap.put(FRAME_ID_ENERGY_SCAN_RESULT_HANDLER, EzspEnergyScanResultHandler.class);
290301
ezspHandlerMap.put(FRAME_ID_ERASE_KEY_TABLE_ENTRY, EzspEraseKeyTableEntryResponse.class);
302+
ezspHandlerMap.put(FRAME_ID_EXPORT_KEY, EzspExportKeyResponse.class);
291303
ezspHandlerMap.put(FRAME_ID_FIND_AND_REJOIN_NETWORK, EzspFindAndRejoinNetworkResponse.class);
292304
ezspHandlerMap.put(FRAME_ID_FIND_KEY_TABLE_ENTRY, EzspFindKeyTableEntryResponse.class);
293305
ezspHandlerMap.put(FRAME_ID_FORM_NETWORK, EzspFormNetworkResponse.class);
@@ -310,6 +322,7 @@ public abstract class EzspFrame {
310322
ezspHandlerMap.put(FRAME_ID_GET_FIRST_BEACON, EzspGetFirstBeaconResponse.class);
311323
ezspHandlerMap.put(FRAME_ID_GET_KEY, EzspGetKeyResponse.class);
312324
ezspHandlerMap.put(FRAME_ID_GET_KEY_TABLE_ENTRY, EzspGetKeyTableEntryResponse.class);
325+
ezspHandlerMap.put(FRAME_ID_IMPORT_KEY, EzspImportKeyResponse.class);
313326
ezspHandlerMap.put(FRAME_ID_GET_LIBRARY_STATUS, EzspGetLibraryStatusResponse.class);
314327
ezspHandlerMap.put(FRAME_ID_GET_MFG_TOKEN, EzspGetMfgTokenResponse.class);
315328
ezspHandlerMap.put(FRAME_ID_GET_MULTICAST_TABLE_ENTRY, EzspGetMulticastTableEntryResponse.class);
@@ -392,6 +405,15 @@ public abstract class EzspFrame {
392405
ezspHandlerMap.put(FRAME_ID_SEND_REPLY, EzspSendReplyResponse.class);
393406
ezspHandlerMap.put(FRAME_ID_SEND_TRUST_CENTER_LINK_KEY, EzspSendTrustCenterLinkKeyResponse.class);
394407
ezspHandlerMap.put(FRAME_ID_SEND_UNICAST, EzspSendUnicastResponse.class);
408+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_CHECK_KEY_CONTEXT, EzspSecManCheckKeyContextResponse.class);
409+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_EXPORT_LINK_KEY_BY_EUI, EzspSecManExportLinkKeyByEuiResponse.class);
410+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_EXPORT_LINK_KEY_BY_INDEX, EzspSecManExportLinkKeyByIndexResponse.class);
411+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_EXPORT_TRANSIENT_KEY_BY_EUI, EzspSecManExportTransientKeyByEuiResponse.class);
412+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_EXPORT_TRANSIENT_KEY_BY_INDEX, EzspSecManExportTransientKeyByIndexResponse.class);
413+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_GET_APS_KEY_INFO, EzspSecManGetApsKeyInfoResponse.class);
414+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_GET_NETWORK_KEY_INFO, EzspSecManGetNetworkKeyInfoResponse.class);
415+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_IMPORT_LINK_KEY, EzspSecManImportLinkKeyResponse.class);
416+
ezspHandlerMap.put(FRAME_ID_SEC_MAN_IMPORT_TRANSIENT_KEY, EzspSecManImportTransientKeyResponse.class);
395417
ezspHandlerMap.put(FRAME_ID_SET_ADDRESS_TABLE_REMOTE_EUI64, EzspSetAddressTableRemoteEui64Response.class);
396418
ezspHandlerMap.put(FRAME_ID_SET_ADDRESS_TABLE_REMOTE_NODE_ID, EzspSetAddressTableRemoteNodeIdResponse.class);
397419
ezspHandlerMap.put(FRAME_ID_SET_BINDING, EzspSetBindingResponse.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Copyright (c) 2016-2026 by the respective copyright holders.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*/
8+
package com.zsmartsystems.zigbee.dongle.ember.ezsp.command;
9+
10+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.EzspFrameRequest;
11+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspSecurityManagerContext;
12+
import com.zsmartsystems.zigbee.dongle.ember.internal.serializer.EzspSerializer;
13+
14+
/**
15+
* Class to implement the Ember EZSP command <b>exportKey</b>.
16+
* <p>
17+
* Exports a key from the security manager based on the provided context.
18+
* <p>
19+
* This class provides methods for processing EZSP commands.
20+
* <p>
21+
* Note that this code is autogenerated. Manual changes may be overwritten.
22+
*
23+
* @author Chris Jackson - Initial contribution of Java code generator
24+
*/
25+
public class EzspExportKeyRequest extends EzspFrameRequest {
26+
public static final int FRAME_ID = 0x0114;
27+
28+
/**
29+
* The security manager context specifying which key to export.
30+
* <p>
31+
* EZSP type is <i>EzspSecurityManagerContext</i> - Java type is {@link EzspSecurityManagerContext}
32+
*/
33+
private EzspSecurityManagerContext context;
34+
35+
/**
36+
* Serialiser used to serialise to binary line data
37+
*/
38+
private EzspSerializer serializer;
39+
40+
/**
41+
* Request constructor
42+
*/
43+
public EzspExportKeyRequest() {
44+
frameId = FRAME_ID;
45+
serializer = new EzspSerializer();
46+
}
47+
48+
/**
49+
* The security manager context specifying which key to export.
50+
* <p>
51+
* EZSP type is <i>EzspSecurityManagerContext</i> - Java type is {@link EzspSecurityManagerContext}
52+
*
53+
* @return the current context as {@link EzspSecurityManagerContext}
54+
*/
55+
public EzspSecurityManagerContext getContext() {
56+
return context;
57+
}
58+
59+
/**
60+
* The security manager context specifying which key to export.
61+
*
62+
* @param context the context to set as {@link EzspSecurityManagerContext}
63+
*/
64+
public void setContext(EzspSecurityManagerContext context) {
65+
this.context = context;
66+
}
67+
68+
@Override
69+
public int[] serialize() {
70+
// Serialize the header
71+
serializeHeader(serializer);
72+
73+
// Serialize the fields
74+
context.serialize(serializer);
75+
return serializer.getPayload();
76+
}
77+
78+
@Override
79+
public String toString() {
80+
final StringBuilder builder = new StringBuilder(80);
81+
builder.append("EzspExportKeyRequest [networkId=");
82+
builder.append(networkId);
83+
builder.append(", context=");
84+
builder.append(context);
85+
builder.append(']');
86+
return builder.toString();
87+
}
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Copyright (c) 2016-2026 by the respective copyright holders.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*/
8+
package com.zsmartsystems.zigbee.dongle.ember.ezsp.command;
9+
10+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.EzspFrameResponse;
11+
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EmberKeyData;
12+
13+
/**
14+
* Class to implement the Ember EZSP command <b>exportKey</b>.
15+
* <p>
16+
* Exports a key from the security manager based on the provided context.
17+
* <p>
18+
* This class provides methods for processing EZSP commands.
19+
* <p>
20+
* Note that this code is autogenerated. Manual changes may be overwritten.
21+
*
22+
* @author Chris Jackson - Initial contribution of Java code generator
23+
*/
24+
public class EzspExportKeyResponse extends EzspFrameResponse {
25+
public static final int FRAME_ID = 0x0114;
26+
27+
/**
28+
* The success or failure code of the operation.
29+
* <p>
30+
* EZSP type is <i>sl_status_t</i> - Java type is {@link int}
31+
*/
32+
private int slStatus;
33+
34+
/**
35+
* The exported key data.
36+
* <p>
37+
* EZSP type is <i>EmberKeyData</i> - Java type is {@link EmberKeyData}
38+
*/
39+
private EmberKeyData key;
40+
41+
/**
42+
* Response and Handler constructor
43+
*/
44+
public EzspExportKeyResponse(int[] inputBuffer) {
45+
// Super creates deserializer and reads header fields
46+
super(inputBuffer);
47+
48+
// Deserialize the fields
49+
slStatus = deserializer.deserializeUInt32();
50+
key = deserializer.deserializeEmberKeyData();
51+
}
52+
53+
/**
54+
* The success or failure code of the operation.
55+
* <p>
56+
* EZSP type is <i>sl_status_t</i> - Java type is {@link int}
57+
*
58+
* @return the current slStatus as {@link int}
59+
*/
60+
public int getSlStatus() {
61+
return slStatus;
62+
}
63+
64+
/**
65+
* The success or failure code of the operation.
66+
*
67+
* @param slStatus the slStatus to set as {@link int}
68+
*/
69+
public void setSlStatus(int slStatus) {
70+
this.slStatus = slStatus;
71+
}
72+
73+
/**
74+
* The exported key data.
75+
* <p>
76+
* EZSP type is <i>EmberKeyData</i> - Java type is {@link EmberKeyData}
77+
*
78+
* @return the current key as {@link EmberKeyData}
79+
*/
80+
public EmberKeyData getKey() {
81+
return key;
82+
}
83+
84+
/**
85+
* The exported key data.
86+
*
87+
* @param key the key to set as {@link EmberKeyData}
88+
*/
89+
public void setKey(EmberKeyData key) {
90+
this.key = key;
91+
}
92+
93+
@Override
94+
public String toString() {
95+
final StringBuilder builder = new StringBuilder(96);
96+
builder.append("EzspExportKeyResponse [networkId=");
97+
builder.append(networkId);
98+
builder.append(", slStatus=");
99+
builder.append(String.format("%08X", slStatus));
100+
builder.append(", key=");
101+
builder.append(key);
102+
builder.append(']');
103+
return builder.toString();
104+
}
105+
}

0 commit comments

Comments
 (0)