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

Ember: bugfixes #1049

Merged
merged 1 commit into from
May 6, 2024
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
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
];