Skip to content

Commit

Permalink
feat: add tryUnzipFirmwareFile utility (#7372)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Nov 6, 2024
1 parent b7b34e1 commit 8144eee
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 9 deletions.
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 @@ -94,6 +94,7 @@
"alcalzone-shared": "^5.0.0",
"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 @@ -5250,6 +5251,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

0 comments on commit 8144eee

Please sign in to comment.