From 1663ca3ca299776122613d16ab8dfb6797651072 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Tue, 1 Dec 2020 17:10:52 +0100 Subject: [PATCH 1/2] feat: support gecko bootloader firmwares --- docs/api/node.md | 1 + packages/core/src/util/firmware.ts | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/api/node.md b/docs/api/node.md index fe9a362cba11..419609f4c23a 100644 --- a/docs/api/node.md +++ b/docs/api/node.md @@ -143,6 +143,7 @@ extractFirmware(rawData: Buffer, format: FirmwareFileFormat): Firmware - `"aeotec"` - A Windows executable (`.exe` or `.ex_`) that contains Aeotec's upload tool - `"otz"` - A compressed firmware file in Intel HEX format - `"ota"` or `"hex"` - An uncompressed firmware file in Intel HEX format +- `"gecko"` - A binary gecko bootloader firmware file with `.gbl` extension > [!NOTE] > `.hec` firmware update files are encrypted with proprietary encryption and not supported by `zwave-js` diff --git a/packages/core/src/util/firmware.ts b/packages/core/src/util/firmware.ts index d2cca022da99..28b181afceed 100644 --- a/packages/core/src/util/firmware.ts +++ b/packages/core/src/util/firmware.ts @@ -2,13 +2,20 @@ import MemoryMap from "nrf-intel-hex"; import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError"; -export type FirmwareFileFormat = "aeotec" | "otz" | "ota" | "hex"; +export type FirmwareFileFormat = "aeotec" | "otz" | "ota" | "hex" | "gecko"; export interface Firmware { data: Buffer; firmwareTarget?: number; } +const firmwareIndicators = { + // All aeotec updater exes contain this text + aeotec: Buffer.from("Aeon Labs", "utf8"), + // This seems to be the standard beginning of a gecko bootloader firmware + gecko: 0xeb17a603, +}; + /** * Guess the firmware format based on filename and firmware buffer * @@ -21,7 +28,7 @@ export function guessFirmwareFileFormat( ): FirmwareFileFormat { if ( (filename.endsWith(".exe") || filename.endsWith(".ex_")) && - rawData.includes(Buffer.from("Aeon Labs", "utf8")) + rawData.includes(firmwareIndicators.aeotec) ) { return "aeotec"; } else if (/\.(hex|ota|otz)$/.test(filename)) { @@ -31,6 +38,11 @@ export function guessFirmwareFileFormat( "Encrypted .hec firmware files are not supported", ZWaveErrorCodes.Unsupported_Firmware_Format, ); + } else if ( + filename.endsWith(".gbl") && + rawData.readUInt32BE(0) === firmwareIndicators.gecko + ) { + return "gecko"; } else { throw new ZWaveError( "Could not detect firmware format", @@ -44,6 +56,7 @@ export function guessFirmwareFileFormat( * - `"aeotec"` - A Windows executable (.exe or .ex_) that contains Aeotec's upload tool * - `"otz"` - A compressed firmware file in Intel HEX format * - `"ota"` or `"hex"` - An uncompressed firmware file in Intel HEX format + * - `"gecko"` - A binary gecko bootloader firmware file with `.gbl` extension * * The returned firmware data and target can be used to start a firmware update process with `node.beginFirmwareUpdate` */ @@ -58,6 +71,10 @@ export function extractFirmware( case "ota": case "hex": return extractFirmwareHEX(rawData); + case "gecko": + // There is no description for the file contents, so we + // have to assume this is for firmware target 0 + return { data: rawData }; } } From 2b5039364f3d21a354e1aa61cc6085a20d7ca714 Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Tue, 8 Dec 2020 14:48:06 +0100 Subject: [PATCH 2/2] fix: support aeotec updaters with checksum and target byte --- packages/core/src/util/firmware.ts | 49 +++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/core/src/util/firmware.ts b/packages/core/src/util/firmware.ts index 28b181afceed..1d13effe336d 100644 --- a/packages/core/src/util/firmware.ts +++ b/packages/core/src/util/firmware.ts @@ -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"; @@ -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, }; @@ -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, @@ -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( @@ -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 {