Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EZSP adapter: backup and experimental EZSPv7,v6,v5 support #598

Merged
merged 7 commits into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/adapter/ezsp/adapter/backup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* istanbul ignore file */
import Debug from "debug";
import {Driver} from '../driver';
import * as Models from "../../../models";
import {EmberKeyType, EmberKeyStruct, EmberNetworkParameters} from '../driver/types';
import {channelsMask2list} from '../driver/utils';


export class EZSPAdapterBackup {
private driver: Driver;
private defaultPath: string;
private debug = Debug("zigbee-herdsman:adapter:ezsp:backup");

public constructor(driver: Driver, path: string) {
this.driver = driver;
this.defaultPath = path;
}

public async createBackup(): Promise<Models.Backup> {
this.debug("creating backup");
const version: number = await this.driver.ezsp.version();
const linkResult = await this.driver.ezsp.execCommand('getKey', {keyType: EmberKeyType.TRUST_CENTER_LINK_KEY});
const trustCenterLinkKey: EmberKeyStruct = linkResult.keyStruct;
const netParams = await this.driver.ezsp.execCommand('getNetworkParameters');
const networkParams: EmberNetworkParameters = netParams.parameters;
const netResult = await this.driver.ezsp.execCommand('getKey', {keyType: EmberKeyType.CURRENT_NETWORK_KEY});
const networkKey: EmberKeyStruct = netResult.keyStruct;
const ieee = (await this.driver.ezsp.execCommand('getEui64')).eui64;
/* return backup structure */
/* istanbul ignore next */
return {
ezsp: {
version: version,
hashed_tclk: Buffer.from(trustCenterLinkKey.key.contents),
},
networkOptions: {
panId: networkParams.panId,
extendedPanId: Buffer.from(networkParams.extendedPanId),
channelList: channelsMask2list(networkParams.channels),
networkKey: Buffer.from(networkKey.key.contents),
networkKeyDistribute: true,
},
logicalChannel: networkParams.radioChannel,
networkKeyInfo: {
sequenceNumber: networkKey.sequenceNumber,
frameCounter: networkKey.outgoingFrameCounter
},
securityLevel: 5,
networkUpdateId: networkParams.nwkUpdateId,
coordinatorIeeeAddress: ieee,
devices: []
};
}
}
13 changes: 5 additions & 8 deletions src/adapter/ezsp/adapter/ezspAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {Waitress, Wait, RealpathSync} from '../../../utils';
import * as Models from "../../../models";
import SerialPortUtils from '../../serialPortUtils';
import SocketPortUtils from '../../socketPortUtils';
import crypto from 'crypto';
import {EZSPAdapterBackup} from './backup';


const autoDetectDefinitions = [
Expand All @@ -39,6 +39,7 @@ class EZSPAdapter extends Adapter {
private port: SerialPortOptions;
private waitress: Waitress<Events.ZclDataPayload, WaitressMatcher>;
private interpanLock: boolean;
private backupMan: EZSPAdapterBackup;

public constructor(networkOptions: NetworkOptions,
serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) {
Expand All @@ -52,10 +53,10 @@ class EZSPAdapter extends Adapter {
this.driver.on('deviceJoined', this.handleDeviceJoin.bind(this));
this.driver.on('deviceLeft', this.handleDeviceLeft.bind(this));
this.driver.on('incomingMessage', this.processMessage.bind(this));
this.backupMan = new EZSPAdapterBackup(this.driver, backupPath);
}

private async processMessage(frame: EmberIncomingMessage) {
// todo
debug(`processMessage: ${JSON.stringify(frame)}`);
if (frame.apsFrame.profileId == 0) {
if (
Expand Down Expand Up @@ -114,7 +115,6 @@ class EZSPAdapter extends Adapter {
}

private async handleDeviceJoin(arr: any[]) {
// todo
let [nwk, ieee] = arr;
debug('Device join request received: %s %s', nwk, ieee.toString('hex'));
const payload: Events.DeviceJoinedPayload = {
Expand All @@ -130,7 +130,6 @@ class EZSPAdapter extends Adapter {
}

private handleDeviceLeft(arr: any[]) {
// todo
let [nwk, ieee] = arr;
debug('Device left network request received: %s %s', nwk, ieee);

Expand Down Expand Up @@ -235,7 +234,6 @@ class EZSPAdapter extends Adapter {
}

public async getCoordinatorVersion(): Promise<CoordinatorVersion> {
// todo
return {type: `EZSP v${this.driver.version.product}`, meta: this.driver.version};
}

Expand Down Expand Up @@ -551,12 +549,11 @@ class EZSPAdapter extends Adapter {
}

public async supportsBackup(): Promise<boolean> {
//todo
return false;
return true;
}

public async backup(): Promise<Models.Backup> {
throw new Error("This adapter does not support backup");
return this.backupMan.createBackup();
}

public async restoreChannelInterPAN(): Promise<void> {
Expand Down
31 changes: 21 additions & 10 deletions src/adapter/ezsp/driver/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ export class Driver extends EventEmitter {

await this.ezsp.updatePolicies();

await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE, 82);
await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE, 82);
//await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_OUTGOING_TRANSFER_SIZE, 82);
//await this.ezsp.setValue(EzspValueId.VALUE_MAXIMUM_INCOMING_TRANSFER_SIZE, 82);
await this.ezsp.setValue(EzspValueId.VALUE_END_DEVICE_KEEP_ALIVE_SUPPORT_MODE, 3);
await this.ezsp.setValue(EzspValueId.VALUE_CCA_THRESHOLD, 0);

Expand Down Expand Up @@ -354,6 +354,14 @@ export class Driver extends EventEmitter {
debug.log(`stackStatusHandler: ${EmberStatus.valueToName(EmberStatus, frame.status)}`);
break;
}
case (frameName === 'childJoinHandler'): {
if (!frame.joining) {
this.handleNodeLeft(frame.childId, frame.childEui64);
} else {
this.handleNodeJoined(frame.childId, frame.childEui64);
}
break;
}
default:
// <=== Application frame 35 (childJoinHandler) received: 00013e9c2ebd08feff9ffd9004 +1ms
// <=== Application frame 35 (childJoinHandler) parsed: 0,1,39998,144,253,159,255,254,8,189,46,4 +1ms
Expand Down Expand Up @@ -456,12 +464,13 @@ export class Driver extends EventEmitter {
} else {
eui64 = await this.networkIdToEUI64(nwk);
}
if (this.ezsp.ezspV < 8) {
// const route = this.eui64ToRelays.get(eui64.toString());
// if (route) {
// const = await this.ezsp.execCommand('setSourceRoute', {eui64});
// // }
}
await this.ezsp.execCommand('setExtendedTimeout', {remoteEui64: eui64, extendedTimeout: true});
// for old emberznet < 8
// const route = this.eui64ToRelays.get(eui64.toString());
// if (route) {
// const [status] = await this.ezsp.execCommand('setSourceRoute', eui64, );
// }
const result = await this.ezsp.sendUnicast(this.direct, nwk, apsFrame, seq, data);
return result.status == EmberStatus.SUCCESS;
} catch (e) {
Expand Down Expand Up @@ -590,9 +599,11 @@ export class Driver extends EventEmitter {
if (result.status !== EmberStatus.SUCCESS) {
throw new Error(`Add Transient Link Key for '${ieee}' failed`);
}
await this.ezsp.setPolicy(EzspPolicyId.TRUST_CENTER_POLICY,
EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS | EzspDecisionBitmask.ALLOW_JOINS);
//| EzspDecisionBitmask.JOINS_USE_INSTALL_CODE_KEY
if (this.ezsp.ezspV >= 8) {
await this.ezsp.setPolicy(EzspPolicyId.TRUST_CENTER_POLICY,
EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS | EzspDecisionBitmask.ALLOW_JOINS);
//| EzspDecisionBitmask.JOINS_USE_INSTALL_CODE_KEY
}
}

public async permitJoining(seconds: number): Promise<EZSPFrameData> {
Expand Down
17 changes: 11 additions & 6 deletions src/adapter/ezsp/driver/ezsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ export class Ezsp extends EventEmitter {
debug.log('Set %s = %s', t.EzspValueId.valueToName(t.EzspValueId, valueId), value);
const ret = await this.execCommand('setValue', {valueId, value});
console.assert(ret.status === EmberStatus.SUCCESS,
`Command (setValue) returned unexpected state: ${ret}`);
`Command (setValue) returned unexpected state: ${ret.status}`);

return ret;
}
Expand Down Expand Up @@ -449,7 +449,7 @@ export class Ezsp extends EventEmitter {

async updatePolicies(): Promise<void> {
// Set up the policies for what the NCP should do.
const policies = [
let policies = [
[EzspPolicyId.BINDING_MODIFICATION_POLICY,
EzspDecisionId.DISALLOW_BINDING_MODIFICATION],
[EzspPolicyId.UNICAST_REPLIES_POLICY, EzspDecisionId.HOST_WILL_NOT_SUPPLY_REPLY],
Expand All @@ -461,11 +461,14 @@ export class Ezsp extends EventEmitter {
[EzspPolicyId.ZLL_POLICY, EzspDecisionId.ALLOW_JOINS],
[EzspPolicyId.TC_REJOINS_USING_WELL_KNOWN_KEY_POLICY, EzspDecisionId.ALLOW_JOINS],
[EzspPolicyId.APP_KEY_REQUEST_POLICY, EzspDecisionId.ALLOW_APP_KEY_REQUESTS],
[EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS
| EzspDecisionBitmask.ALLOW_JOINS],
[EzspPolicyId.TC_KEY_REQUEST_POLICY, EzspDecisionId.ALLOW_TC_KEY_REQUESTS],
];

if (this.ezspV >= 8) {
policies = policies.concat([
[EzspPolicyId.TRUST_CENTER_POLICY, EzspDecisionBitmask.ALLOW_UNSECURED_REJOINS
| EzspDecisionBitmask.ALLOW_JOINS],
]);
}
for (const [policy, value] of policies) {
await this.setPolicy(policy, value);
}
Expand Down Expand Up @@ -565,7 +568,9 @@ export class Ezsp extends EventEmitter {
if (res.status != EmberStatus.SUCCESS) {
debug.log("Couldn't set concentrator type %s: %s", true, JSON.stringify(res));
}
await this.execCommand('setSourceRouteDiscoveryMode', {mode: 1});
if (this.ezspV >= 8) {
await this.execCommand('setSourceRouteDiscoveryMode', {mode: 1});
}
}

public waitFor(frameId: string|number, sequence: number | null, timeout = 10000)
Expand Down
3 changes: 3 additions & 0 deletions src/adapter/ezsp/driver/types/struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ export class EmberCurrentSecurityState extends EzspStruct {
}

export class EmberKeyStruct extends EzspStruct {
public key: EmberKeyData;
public outgoingFrameCounter: number;
public sequenceNumber: number;
// A structure containing a key and its associated data.
static _fields = [
// A bitmask indicating the presence of data within the various fields
Expand Down
9 changes: 7 additions & 2 deletions src/adapter/ezsp/driver/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ function ember_security(config: Record<string, any>): EmberInitialSecurityState
EmberInitialSecurityBitmask.REQUIRE_ENCRYPTED_KEY |
EmberInitialSecurityBitmask.TRUST_CENTER_USES_HASHED_LINK_KEY);
isc.preconfiguredKey = new EmberKeyData();
//isc.preconfiguredKey.contents = Buffer.from("ZigBeeAlliance09");
isc.preconfiguredKey.contents = randomBytes(16);
isc.networkKey = new EmberKeyData();
isc.networkKey.contents = config.networkKey;
Expand All @@ -68,4 +67,10 @@ function ember_security(config: Record<string, any>): EmberInitialSecurityState
return isc;
}

export {crc16ccitt, ember_security};
const allChannels = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26];
function channelsMask2list(channelMask: number): number[] {
return allChannels.map((channel: number) => ((2 ** channel) & channelMask) ? channel : null).filter((x)=>x);
}


export {crc16ccitt, ember_security, channelsMask2list};
6 changes: 5 additions & 1 deletion src/models/backup-storage-unified.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export interface UnifiedBackupStorage {
internal: {
/* zigbee-herdsman specific data */
date: string;
znpVersion: number;
znpVersion?: number;
ezspVersion?: number;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
Expand All @@ -22,6 +23,9 @@ export interface UnifiedBackupStorage {
zstack?: {
tclk_seed?: string;
};
ezsp?: {
hashed_tclk?: string;
};
};
coordinator_ieee: string;
pan_id: string;
Expand Down
4 changes: 4 additions & 0 deletions src/models/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ export interface Backup {
version?: ZnpVersion;
trustCenterLinkKeySeed?: Buffer;
};
ezsp?: {
version?: number;
hashed_tclk?: Buffer;
};
}
20 changes: 16 additions & 4 deletions src/utils/backup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,25 @@ export const toUnifiedBackup = async (backup: Models.Backup): Promise<Models.Uni
source: `${packageInfo.name}@${packageInfo.version}`,
internal: {
date: new Date().toISOString(),
znpVersion: [null, undefined].includes(backup.znp?.version) ? undefined : backup.znp?.version
...(backup.znp) ? {
znpVersion: [null, undefined].includes(backup.znp?.version) ? undefined : backup.znp?.version
} : undefined,
...(backup.ezsp) ? {
ezspVersion: [null, undefined].includes(backup.ezsp?.version) ? undefined : backup.ezsp?.version
} : undefined,
}
},
stack_specific: {
zstack: {
tclk_seed: backup.znp?.trustCenterLinkKeySeed?.toString("hex") || undefined
}
...(backup.znp) ? {
zstack: {
tclk_seed: backup.znp?.trustCenterLinkKeySeed?.toString("hex") || undefined
}
} : undefined,
...(backup.ezsp) ? {
ezsp: {
hashed_tclk: backup.ezsp?.hashed_tclk?.toString("hex") || undefined
}
} : undefined
},
coordinator_ieee: backup.coordinatorIeeeAddress?.toString("hex") || null,
pan_id: panIdBuffer.toString("hex"),
Expand Down