UNPKG

inventoresed

Version:

Z-Wave driver written entirely in JavaScript/TypeScript

105 lines (94 loc) 2.98 kB
import { extractFirmware, Firmware, guessFirmwareFileFormat, ZWaveError, ZWaveErrorCodes, } from "@zwave-js/core"; import { formatId } from "@zwave-js/shared"; import axios, { AxiosRequestConfig } from "axios"; import crypto from "crypto"; import type { FirmwareUpdateFileInfo, FirmwareUpdateInfo } from "./_Types"; const serviceURL = "https://firmware.zwave-js.io"; const DOWNLOAD_TIMEOUT = 60000; const MAX_FIRMWARE_SIZE = 10 * 1024 * 1024; // 10MB should be enough for any conceivable Z-Wave chip /** * Retrieves the available firmware updates for the node with the given fingerprint. * Returns the service response or `undefined` in case of an error. */ export async function getAvailableFirmwareUpdates( manufacturerId: number, productType: number, productId: number, firmwareVersion: string, apiKey?: string, ): Promise<FirmwareUpdateInfo[]> { const config: AxiosRequestConfig = { method: "post", url: `${serviceURL}/api/v1/updates`, data: { manufacturerId: formatId(manufacturerId), productType: formatId(productType), productId: formatId(productId), firmwareVersion, }, }; if (apiKey) { config.headers = { "X-API-Key": apiKey, }; } const response: FirmwareUpdateInfo[] = (await axios(config)).data; return response; } export async function downloadFirmwareUpdate( file: FirmwareUpdateFileInfo, ): Promise<Firmware> { const [hashAlgorithm, expectedHash] = file.integrity.split(":", 2); if (hashAlgorithm !== "sha256") { throw new ZWaveError( `Unsupported hash algorithm ${hashAlgorithm} for integrity check`, ZWaveErrorCodes.Argument_Invalid, ); } // TODO: Make request abort-able (requires AbortController, Node 14.17+ / Node 16) // Download the firmware file const downloadResponse = await axios.get<Buffer>(file.url, { timeout: DOWNLOAD_TIMEOUT, maxContentLength: MAX_FIRMWARE_SIZE, responseType: "arraybuffer", }); const rawData = downloadResponse.data; // Infer the file type from the content-disposition header or the filename let filename: string; if ( downloadResponse.headers["content-disposition"]?.startsWith( "attachment; filename=", ) ) { filename = downloadResponse.headers["content-disposition"] .split("filename=")[1] .replace(/^"/, "") .replace(/[";]$/, ""); } else { filename = new URL(file.url).pathname; } // Extract the raw data const format = guessFirmwareFileFormat(filename, rawData); const firmware = extractFirmware(rawData, format); // Ensure the hash matches const hasher = crypto.createHash("sha256"); hasher.update(firmware.data); const actualHash = hasher.digest("hex"); if (actualHash !== expectedHash) { throw new ZWaveError( `Integrity check failed. Expected hash ${expectedHash}, got ${actualHash}`, ZWaveErrorCodes.FWUpdateService_IntegrityCheckFailed, ); } return { data: firmware.data, // Don't trust the guessed firmware target, use the one from the provided info firmwareTarget: file.target, }; }