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

feat: add tryUnzipFirmwareFile utility #7372

Merged
merged 1 commit into from
Nov 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
59 changes: 55 additions & 4 deletions docs/api/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,10 @@ extractFirmware(rawData: Buffer, format: FirmwareFileFormat): Firmware
- `"hec"` - An encrypted Intel HEX firmware file
- `"gecko"` - A binary gecko bootloader firmware file with `.gbl` extension

If successful, `extractFirmware` returns an `Firmware` object which can be passed to the `updateFirmware` method.

If no firmware data can be extracted, the method will throw.

> [!ATTENTION] At the moment, only some `.exe` files contain `firmwareTarget` information. **All** other formats only contain the firmware `data`.
> This means that the `firmwareTarget` property usually needs to be provided, unless it is `0`.

Expand All @@ -406,10 +410,6 @@ guessFirmwareFileFormat(filename: string, rawData: Buffer): FirmwareFileFormat
- `filename`: The name of the firmware file (including the extension)
- `rawData`: A buffer containing the original firmware update file

If successful, `extractFirmware` returns an `Firmware` object which can be passed to the `updateFirmware` method.

If no firmware data can be extracted, the method will throw.

Example usage:

```ts
Expand Down Expand Up @@ -437,6 +437,57 @@ try {
}
```

In some cases, the firmware update file has to be extracted from a ZIP archive first. Z-Wave JS provides a utility method to do so, which must be used instead of `guessFirmwareFileFormat`:

```ts
tryUnzipFirmwareFile(zipData: Uint8Array): {
filename: string;
format: FirmwareFileFormat;
rawData: Uint8Array;
} | undefined;
```

If the given ZIP archive contains a compatible firmware update file, the method returns an object with the following properties:

- `filename`: The name of the unzipped firmware file.
- `format`: The guessed format of the unzipped firmware file (see `guessFirmwareFileFormat` above)
- `rawData`: A buffer containing the unzipped firmware update file.

Otherwise `undefined` is returned.

The unzipped firmware file can then be passed to `extractFirmware` to get the firmware data. Example usage:

```ts
// Unzip the firmware archive
const unzippedFirmware = tryUnzipFirmwareFile(zipData);
if (!unzippedFirmware) {
// No firmware file found in the ZIP archive, abort update
}

const { filename, format, rawData } = unzippedFirmware;
// Extract the firmware from a given firmware file
let actualFirmware: Firmware;
try {
actualFirmware = extractFirmware(rawData, format);
} catch (e) {
// handle the error, then abort the update
}

if (actualFirmware.firmwareTarget == undefined) {
actualFirmware.firmwareTarget = getFirmwareTargetSomehow();
}

// try the update
try {
const result = await this.driver.controller.nodes
.get(nodeId)!
.updateFirmware([actualFirmware]);
// check result
} catch (e) {
// handle error
}
```

### `abortFirmwareUpdate`

```ts
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"alcalzone-shared": "^4.0.8",
"ansi-colors": "^4.1.3",
"dayjs": "^1.11.13",
"fflate": "^0.8.2",
"logform": "^2.6.1",
"nrf-intel-hex": "^1.4.0",
"reflect-metadata": "^0.2.2",
Expand Down
44 changes: 39 additions & 5 deletions packages/core/src/util/firmware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getErrorMessage, isUint8Array } from "@zwave-js/shared";
import { Bytes } from "@zwave-js/shared/safe";
import { unzipSync } from "fflate";
import * as crypto from "node:crypto";
import { ZWaveError, ZWaveErrorCodes } from "../error/ZWaveError.js";
import type { Firmware, FirmwareFileFormat } from "./_Types.js";
Expand Down Expand Up @@ -53,11 +54,44 @@ export function guessFirmwareFileFormat(
.equals(firmwareIndicators.hec)
) {
return "hec";
} else {
throw new ZWaveError(
"Could not detect firmware format",
ZWaveErrorCodes.Invalid_Firmware_File,
);
}

throw new ZWaveError(
"Could not detect firmware format",
ZWaveErrorCodes.Invalid_Firmware_File,
);
}

/**
* Given the contents of a ZIP archive with a compatible firmware file,
* this function extracts the firmware data and guesses the firmware format
* using {@link guessFirmwareFileFormat}.
*
* @returns An object containing the filename, guessed format and unzipped data
* of the firmware file from the ZIP archive, or `undefined` if no compatible
* firmware file could be extracted.
*/
export function tryUnzipFirmwareFile(zipData: Uint8Array): {
filename: string;
format: FirmwareFileFormat;
rawData: Uint8Array;
} | undefined {
// Extract files we can work with
const unzipped = unzipSync(zipData, {
filter: (file) => {
return /\.(hex|exe|ex_|ota|otz|hec|gbl|bin)$/.test(file.name);
},
});
if (Object.keys(unzipped).length === 1) {
// Exactly one file was extracted, inspect that
const filename = Object.keys(unzipped)[0];
const rawData = unzipped[filename];
try {
const format = guessFirmwareFileFormat(filename, rawData);
return { filename, format, rawData };
} catch {
return;
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2633,6 +2633,7 @@ __metadata:
ansi-colors: "npm:^4.1.3"
dayjs: "npm:^1.11.13"
del-cli: "npm:^6.0.0"
fflate: "npm:^0.8.2"
logform: "npm:^2.6.1"
nrf-intel-hex: "npm:^1.4.0"
reflect-metadata: "npm:^0.2.2"
Expand Down Expand Up @@ -5243,6 +5244,13 @@ __metadata:
languageName: node
linkType: hard

"fflate@npm:^0.8.2":
version: 0.8.2
resolution: "fflate@npm:0.8.2"
checksum: 10/2bd26ba6d235d428de793c6a0cd1aaa96a06269ebd4e21b46c8fd1bd136abc631acf27e188d47c3936db090bf3e1ede11d15ce9eae9bffdc4bfe1b9dc66ca9cb
languageName: node
linkType: hard

"figures@npm:^2.0.0":
version: 2.0.0
resolution: "figures@npm:2.0.0"
Expand Down
Loading