192 lines
5.4 KiB
JavaScript
192 lines
5.4 KiB
JavaScript
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,
|
|
}
|