Skip to content

Commit

Permalink
fix: Fixes for ember (#1049)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nerivec committed May 6, 2024
1 parent a731e60 commit 0d5ef04
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 36 deletions.
48 changes: 25 additions & 23 deletions src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2914,29 +2914,31 @@ export class EmberAdapter extends Adapter {
// queued
public async addInstallCode(ieeeAddress: string, key: Buffer): Promise<void> {
if (!key) {
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; no code given.`);
throw new Error(`[ADD INSTALL CODE] Failed for '${ieeeAddress}'; no code given.`);
}

if (EMBER_INSTALL_CODE_SIZES.indexOf(key.length) === -1) {
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; invalid code size.`);
}

// Reverse the bits in a byte (uint8_t)
const reverse = (b: number): number => {
return (((b * 0x0802 & 0x22110) | (b * 0x8020 & 0x88440)) * 0x10101 >> 16) & 0xFF;
};
let crc = 0xFFFF;// uint16_t
// codes with CRC, check CRC before sending to NCP, otherwise let NCP handle
if (EMBER_INSTALL_CODE_SIZES.indexOf(key.length) !== -1) {
// Reverse the bits in a byte (uint8_t)
const reverse = (b: number): number => {
return (((b * 0x0802 & 0x22110) | (b * 0x8020 & 0x88440)) * 0x10101 >> 16) & 0xFF;
};
let crc = 0xFFFF;// uint16_t

// Compute the CRC and verify that it matches.
// The bit reversals, byte swap, and ones' complement are due to differences between halCommonCrc16 and the Smart Energy version.
for (let index = 0; index < (key.length - EMBER_INSTALL_CODE_CRC_SIZE); index++) {
crc = halCommonCrc16(reverse(key[index]), crc);
}
// Compute the CRC and verify that it matches.
// The bit reversals, byte swap, and ones' complement are due to differences between halCommonCrc16 and the Smart Energy version.
for (let index = 0; index < (key.length - EMBER_INSTALL_CODE_CRC_SIZE); index++) {
crc = halCommonCrc16(reverse(key[index]), crc);
}

crc = (~highLowToInt(reverse(lowByte(crc)), reverse(highByte(crc)))) & 0xFFFF;
crc = (~highLowToInt(reverse(lowByte(crc)), reverse(highByte(crc)))) & 0xFFFF;

if (key[key.length - EMBER_INSTALL_CODE_CRC_SIZE] !== lowByte(crc) || key[key.length - EMBER_INSTALL_CODE_CRC_SIZE + 1] !== highByte(crc)) {
throw new Error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}"; invalid code CRC.`);
if (key[key.length - EMBER_INSTALL_CODE_CRC_SIZE] !== lowByte(crc)
|| key[key.length - EMBER_INSTALL_CODE_CRC_SIZE + 1] !== highByte(crc)) {
throw new Error(`[ADD INSTALL CODE] Failed for '${ieeeAddress}'; invalid code CRC.`);
} else {
logger.debug(`[ADD INSTALL CODE] CRC validated for '${ieeeAddress}'.`, NS);
}
}

return new Promise<void>((resolve, reject): void => {
Expand All @@ -2946,7 +2948,7 @@ export class EmberAdapter extends Adapter {
const [aesStatus, keyContents] = (await this.emberAesHashSimple(key));

if (aesStatus !== EmberStatus.SUCCESS) {
logger.error(`[ADD INSTALL CODE] Failed AES hash for "${ieeeAddress}" with status=${EmberStatus[aesStatus]}.`, NS);
logger.error(`[ADD INSTALL CODE] Failed AES hash for '${ieeeAddress}' with status=${EmberStatus[aesStatus]}.`, NS);
return aesStatus;
}

Expand All @@ -2955,9 +2957,9 @@ export class EmberAdapter extends Adapter {
const impStatus = (await this.ezsp.ezspImportTransientKey(ieeeAddress, {contents: keyContents}, SecManFlag.NONE));

if (impStatus == SLStatus.OK) {
logger.debug(`[ADD INSTALL CODE] Success for "${ieeeAddress}".`, NS);
logger.debug(`[ADD INSTALL CODE] Success for '${ieeeAddress}'.`, NS);
} else {
logger.error(`[ADD INSTALL CODE] Failed for "${ieeeAddress}" with status=${SLStatus[impStatus]}.`, NS);
logger.error(`[ADD INSTALL CODE] Failed for '${ieeeAddress}' with status=${SLStatus[impStatus]}.`, NS);
return EmberStatus.ERR_FATAL;
}

Expand Down Expand Up @@ -3733,7 +3735,7 @@ export class EmberAdapter extends Adapter {

if (CHECK_APS_PAYLOAD_LENGTH) {
const maxPayloadLength = (
await this.maximumApsPayloadLength(EmberOutgoingMessageType.BROADCAST, EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS, apsFrame)
await this.maximumApsPayloadLength(EmberOutgoingMessageType.BROADCAST, destination, apsFrame)
);

if (data.length > maxPayloadLength) {
Expand All @@ -3745,7 +3747,7 @@ export class EmberAdapter extends Adapter {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [status, messageTag] = (await this.ezsp.send(
EmberOutgoingMessageType.BROADCAST,
EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS,
destination,
apsFrame,
data,
0,// alias
Expand Down
4 changes: 2 additions & 2 deletions src/adapter/ember/uart/ash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,11 @@ export class UartAsh extends EventEmitter {
logger.error(`Error while flushing before start: ${err}`, NS);
}

this.sendExec();

// block til RSTACK, fatal error or timeout
// NOTE: on average, this seems to take around 1000ms when successful
for (let i = 0; i < CONFIG_TIME_RST; i += CONFIG_TIME_RST_CHECK) {
this.sendExec();

if ((this.flags & Flag.CONNECTED)) {
logger.info(`======== ASH started ========`, NS);

Expand Down
23 changes: 12 additions & 11 deletions test/adapter/ember/ash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ import {EzspStatus} from "../../../src/adapter/ember/enums";
import {EzspBuffer} from "../../../src/adapter/ember/uart/queues";
import {UartAsh} from "../../../src/adapter/ember/uart/ash";
import {EZSP_HOST_RX_POOL_SIZE, TX_POOL_BUFFERS} from "../../../src/adapter/ember/uart/consts";
import {RECD_RSTACK_BYTES, SEND_RST_BYTES, NCP_ACK_FIRST, adapterSONOFFDongleE} from "./consts";
import {RECD_RSTACK_BYTES, SEND_RST_BYTES, NCP_ACK_FIRST, adapterSONOFFDongleE, ASH_ACK_FIRST} from "./consts";
import {EzspBuffalo} from "../../../src/adapter/ember/ezsp/buffalo.ts";
import {lowByte} from '../../../src/adapter/ember/utils/math';
import {EzspFrameID} from '../../../src/adapter/ember/ezsp/enums.ts';
import {Wait} from '../../../src/utils/';

const consoleLogNative = console.log;

// XXX: Below are copies from uart>ash.ts, should be kept in sync (avoids export)
/** max frames sent without being ACKed (1-7) */
const CONFIG_TX_K = 3;
Expand Down Expand Up @@ -74,11 +72,9 @@ describe('Ember UART ASH Protocol', () => {

beforeAll(async () => {
jest.useRealTimers();// messes with serialport promise handling otherwise?
console.log = jest.fn();
});
afterAll(async () => {
jest.useRealTimers();
console.log = consoleLogNative;
});
beforeEach(() => {
for (const mock of mocks) {
Expand Down Expand Up @@ -156,6 +152,7 @@ describe('Ember UART ASH Protocol', () => {
expect(uartAsh.txQueue.length).toStrictEqual(2);
expect(uartAsh.txFree.length).toStrictEqual(TX_POOL_BUFFERS - 2);
});

it('Reaches CONNECTED state', async () => {
//@ts-expect-error private
const initVariablesSpy = jest.spyOn(uartAsh, 'initVariables');
Expand Down Expand Up @@ -193,25 +190,28 @@ describe('Ember UART ASH Protocol', () => {
const startResult = (await uartAsh.start());

expect(startResult).toStrictEqual(EzspStatus.SUCCESS);
expect(sendExecSpy).toHaveBeenCalledTimes(1);
expect(sendExecSpy).toHaveBeenCalled();
await new Promise(setImmediate);// flush
//@ts-expect-error private
expect(uartAsh.serialPort.port.lastWrite).toStrictEqual(Buffer.from(SEND_RST_BYTES));
expect(uartAsh.serialPort.port.recording).toStrictEqual(Buffer.from([...SEND_RST_BYTES, ...ASH_ACK_FIRST]))
expect(uartAsh.connected).toBeTruthy();
expect(uartAsh.counters.txAllFrames).toStrictEqual(1);// RST
expect(uartAsh.counters.txAllFrames).toStrictEqual(2);// RST + ACK
expect(uartAsh.counters.txAckFrames).toStrictEqual(1);// post-RSTACK ACK
expect(uartAsh.counters.rxAllFrames).toStrictEqual(1);// RSTACK

Object.keys(uartAsh.counters).forEach((key) => {
if (key !== 'txAllFrames' && key !== 'rxAllFrames') {
for (const key in uartAsh.counters) {
if (key !== 'txAllFrames' && key !== 'rxAllFrames' && key !== 'txAckFrames') {
expect(uartAsh.counters[key]).toStrictEqual(0);
}
});
}

await uartAsh.stop();

expect(initVariablesSpy).toHaveBeenCalledTimes(2);// always called on stop
expect(onPortErrorSpy).toHaveBeenCalledTimes(0);
expect(onPortCloseSpy).toHaveBeenCalledTimes(1);
});

it.skip('Resets but failed to start b/c error in RSTACK frame returned by NCP', async () => {
//@ts-expect-error private
const rejectFrameSpy = jest.spyOn(uartAsh, 'rejectFrame');
Expand Down Expand Up @@ -244,6 +244,7 @@ describe('Ember UART ASH Protocol', () => {
expect(receiveFrameSpy).toHaveLastReturnedWith(EzspStatus.NO_RX_DATA);
expect(uartAsh.connected).toBeFalsy();
});

describe('In CONNECTED state...', () => {
beforeEach(async () => {
const resetResult = (await uartAsh.resetNcp());
Expand Down
11 changes: 11 additions & 0 deletions test/adapter/ember/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,14 @@ export const NCP_ACK_FIRST = [
0x59,// CRC Low
AshReservedByte.FLAG
];
/**
* ACK sent by ASH (Z2M) after RSTACK received.
*
* ACK(0)+
*/
export const ASH_ACK_FIRST = [
AshFrameType.ACK,
0x70,// CRC High
0x78,// CRC Low
AshReservedByte.FLAG
];

0 comments on commit 0d5ef04

Please sign in to comment.