var fs = require('fs').promises; var path = require('path'); var crc = require('crc'); var chokidar = require('chokidar'); var firmwares = [] const parseVersion = (version) => { try { const array = version.split('.') if (!array || array.length != 3) { throw ({ message: 'invalid_version' }) } return { major: array[0], minor: array[1], build: array[2], } } catch (e) { return null; } } const compareVersions = (v1, v2) => { var normalized1 = (v1.major << 16) | (v1.minor << 8) | (v1.build); var normalized2 = (v2.major << 16) | (v2.minor << 8) | (v2.build); return (normalized1 > normalized2) ? -1 : (normalized1 < normalized2) ? 1 : 0; } const validateHardwareVersion = (list, version) => { for (element of list) { if (element.minor == version.minor && element.build == version.build) { return true } } return false } const getBinFiles = async (dir) => { const dirents = await fs.opendir(dir) var files = [] for await (const dirent of dirents) { const filePath = path.join(dir, dirent.name) if (dirent.isDirectory()) { files = files.concat(await getBinFiles(filePath)) } else if (path.extname(dirent.name) === '.bin') { files.push(filePath) } } return files } const validateFirmware = async (filePath) => { const fileBuffer = await fs.readFile(filePath); const stats = await fs.stat(filePath); const startPattern = Buffer.from('5C2A5C', 'hex'); const endPattern = Buffer.from('5C2B5C', 'hex'); var firmware = {} var offset = 0; ///// Check file is bin file if (path.extname(filePath) != '.bin') { throw ({ message: 'not_a_bin_file' }) } ///// Check firmware size if (stats.size < 18) { throw ({ message: 'invalid_firmware' }) } ///// Find patterns first var startMatch = fileBuffer.indexOf(startPattern, 0) var endMatch = fileBuffer.indexOf(endPattern, 0) if (startMatch < 0 || endMatch < 0) { throw ({ message: 'invalid_firmware' }) } ///// Version offset = 0x0000; firmware.version = { major: fileBuffer.readUInt8(offset++), minor: fileBuffer.readUInt8(offset++), build: fileBuffer.readUInt8(offset++) } ///// Hardware Type offset = 0x0003; firmware.hardware_type = fileBuffer.readUInt8(offset++) ///// Module Type offset = 0x0008; firmware.moduleType = fileBuffer.toString('ascii', offset, Math.min(fileBuffer.indexOf(0x00, offset), offset + 10)).trim(); ///// Hardware compatibility offset = startMatch + 3 let compatibility = [] while (offset < endMatch) { compatibility.push({ major: fileBuffer.readUInt8(offset++), minor: fileBuffer.readUInt8(offset++), build: fileBuffer.readUInt8(offset++) }) } firmware.compatibility = compatibility ///// Hash firmware.firmwareHash = crc.crc32(fileBuffer).toString(16) ///// Size firmware.firmwareSize = stats.size ///// Path firmware.path = filePath; return firmware } const init = async (path) => { try { this.path = path; var watcher = chokidar.watch(this.path + '/**/*.bin', { ignored: /^\./, persistent: true }); watcher .on('add', async function (path) { ///// Try validating firmware try { firmwares.push(await validateFirmware(path)) } catch (e) { } }) .on('unlink', async function (path) { ///// Remove firmware from list firmwares = firmwares.filter((element) => { return element.path != path }) }) .on('change', async function (path) { ///// Remove firmware from list firmwares = firmwares.filter((element) => { return element.path != path }) ///// Try validating firmware try { firmwares.push(await validateFirmware(path)) } catch (e) { } }) .on('error', async function (error) { console.error('Error happened', error); }) return null } catch (exception) { return exception.message } } const lookFirmware = (serialNumber, firmwareHash, version, moduleType, hardwareIndex, hardwareVersion) => { try { ///// Retrieve best firmware const firmware = firmwares.filter((element) => { const parsedHardwareVersion = parseVersion(hardwareVersion); if (!hardwareVersion) { return false; } if (!validateHardwareVersion(element.compatibility, parsedHardwareVersion)) { return false; } if (moduleType && element.moduleType != moduleType) { return false; } if (firmwareHash && (firmwareHash != 'init' && element.firmwareHash == firmwareHash)) { return false; } return true; }).sort((f1, f2) => { return compareVersions(f1.version, f2.version); })[0] return firmware } catch (e) { throw new Error(e.message) } } const syncFirmware = async (serialNumber, firmwareHash, moduleType, position, length) => { try { ///// Retrieve firmware const firmware = firmwares.find((element) => { return element.firmwareHash == firmwareHash; }) ///// Check module type if (moduleType && firmware.moduleType != moduleType) { return false; } ///// Read file const fileBuffer = await fs.readFile(firmware.path); return fileBuffer.subarray(parseInt(position), parseInt(position) + parseInt(length)); } catch (e) { throw new Error(e.message); } } module.exports = { init, lookFirmware, syncFirmware, }