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

fix: support Aeotec updaters with checksum and target byte #1194

Merged
merged 5 commits into from
Dec 11, 2020
Merged
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
49 changes: 45 additions & 4 deletions packages/core/src/util/firmware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-expect-error There are no type definitions for nrf-intel-hex
import MemoryMap from "nrf-intel-hex";
import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError";
import { CRC16_CCITT } from "./crc";

export type FirmwareFileFormat = "aeotec" | "otz" | "ota" | "hex" | "gecko";

Expand All @@ -11,7 +12,7 @@ export interface Firmware {

const firmwareIndicators = {
// All aeotec updater exes contain this text
aeotec: Buffer.from("Aeon Labs", "utf8"),
aeotec: Buffer.from("Zensys.ZWave", "utf8"),
// This seems to be the standard beginning of a gecko bootloader firmware
gecko: 0xeb17a603,
};
Expand Down Expand Up @@ -87,10 +88,19 @@ function extractFirmwareAeotec(data: Buffer): Firmware {
);
}

// The Aeotec updaters are .net assemblies which are normally 16-byte-aligned
// The additional firmware data (also 16-byte-aligned), the firmware name (256 bytes)
// and some control bytes are added at the end, so we can deduce which kind of information
// is included here
const numControlBytes = data.length % 16;
// The control bytes are as follows:
// [2 bytes checksum]? [4 bytes offset] [4 bytes length]

// The exe file contains the firmware data and filename at the end
const firmwareStart = data.readUInt32BE(data.length - 8);
const firmwareLength = data.readUInt32BE(data.length - 4);
if (firmwareStart + firmwareLength > data.length - 256 - 8) {

if (firmwareStart + firmwareLength > data.length - 256 - numControlBytes) {
throw new ZWaveError(
"This does not appear to be a valid Aeotec updater (invalid firmware length)!",
ZWaveErrorCodes.Argument_Invalid,
Expand All @@ -101,9 +111,37 @@ function extractFirmwareAeotec(data: Buffer): Firmware {
firmwareStart,
firmwareStart + firmwareLength,
);
const firmwareNameBytes = data.slice(data.length - 256 - 8).slice(0, 256);

const firmwareNameBytes = data
.slice(data.length - 256 - numControlBytes)
.slice(0, 256);

// Some exe files contain a CRC-16 checksum, extract that too and check it
if (numControlBytes === 10) {
const checksum = data.readUInt16BE(data.length - 10);
const actualChecksum = CRC16_CCITT(
Buffer.concat([firmwareData, firmwareNameBytes]),
0xfe95,
);
if (checksum !== actualChecksum) {
throw new ZWaveError(
"This does not appear to be a valid Aeotec updater (invalid checksum)!",
ZWaveErrorCodes.Argument_Invalid,
);
}
}

// Some updaters contain the firmware target in the first byte of the name
// We can't test this, so we have to assume the value translates to a non-printable ASCII char (less than " ")
const firmwareTarget =
firmwareNameBytes[0] < 0x20 ? firmwareNameBytes[0] : undefined;
const firmwareNameOffset = firmwareTarget == undefined ? 0 : 1;

const firmwareName = firmwareNameBytes
.slice(0, firmwareNameBytes.indexOf(0))
.slice(
firmwareNameOffset,
firmwareNameBytes.indexOf(0, firmwareNameOffset),
)
.toString("utf8");
if (!/^[a-zA-Z0-9_]+$/.test(firmwareName)) {
throw new ZWaveError(
Expand All @@ -116,6 +154,9 @@ function extractFirmwareAeotec(data: Buffer): Firmware {
const ret: Firmware = {
data: firmwareData,
};
if (firmwareTarget != undefined) {
ret.firmwareTarget = firmwareTarget;
}
if (/__TargetZwave__/.test(firmwareName)) {
ret.firmwareTarget = 0;
} else {
Expand Down