stk500-esm
Version:
A modern, ESM-compatible, TypeScript implementation of the STK500v1 protocol for programming Arduino boards directly from Node.js or the browser.
128 lines (127 loc) • 5.5 kB
JavaScript
// Ported from https://github.com/bminer/intel-hex.js
const DATA = 0;
const END_OF_FILE = 1;
const EXT_SEGMENT_ADDR = 2;
const START_SEGMENT_ADDR = 3;
const EXT_LINEAR_ADDR = 4;
const START_LINEAR_ADDR = 5;
const EMPTY_VALUE = 0xff;
/**
* Parses Intel HEX format data into a binary buffer.
*
* @param data - The Intel HEX data to parse, as a string or Uint8Array.
* @param bufferSize - The initial size of the buffer to allocate (default: 8192).
* @param addressOffset - An offset to apply to all addresses in the HEX data (default: 0).
* @returns An object containing the parsed binary data and any start addresses found.
* @throws Will throw an error if the HEX data is invalid or parsing fails.
*/
export default function parseIntelHex(data, bufferSize, addressOffset) {
if (data instanceof Uint8Array) {
data = new TextDecoder().decode(data);
}
let buf = new Uint8Array(bufferSize ?? 8192);
let bufLength = 0;
let highAddress = 0;
let startSegmentAddress = null;
let startLinearAddress = null;
const offset = addressOffset ?? 0;
let lineNum = 0;
let pos = 0;
const SMALLEST_LINE = 11;
while (pos + SMALLEST_LINE <= data.length) {
if (data[pos++] !== ":") {
throw new Error(`Line ${lineNum + 1} does not start with a colon (:).`);
}
lineNum++;
const dataLength = parseInt(data.slice(pos, pos + 2), 16);
pos += 2;
const lowAddress = parseInt(data.slice(pos, pos + 4), 16);
pos += 4;
const recordType = parseInt(data.slice(pos, pos + 2), 16);
pos += 2;
const dataField = data.slice(pos, pos + dataLength * 2);
const dataFieldBuf = new Uint8Array(dataLength);
for (let i = 0; i < dataLength; i++) {
dataFieldBuf[i] = parseInt(dataField.slice(i * 2, i * 2 + 2), 16);
}
pos += dataLength * 2;
const checksum = parseInt(data.slice(pos, pos + 2), 16);
pos += 2;
let calcChecksum = (dataLength + (lowAddress >> 8) + lowAddress + recordType) & 0xff;
for (let i = 0; i < dataLength; i++) {
calcChecksum = (calcChecksum + dataFieldBuf[i]) & 0xff;
}
calcChecksum = (0x100 - calcChecksum) & 0xff;
if (checksum !== calcChecksum) {
throw new Error(`Invalid checksum on line ${lineNum}: got ${checksum}, but expected ${calcChecksum}`);
}
switch (recordType) {
case DATA: {
const absoluteAddress = highAddress + lowAddress - offset;
if (absoluteAddress + dataLength >= buf.length) {
const tmp = new Uint8Array((absoluteAddress + dataLength) * 2);
tmp.set(buf.subarray(0, bufLength));
tmp.fill(EMPTY_VALUE, bufLength, absoluteAddress);
tmp.set(dataFieldBuf, absoluteAddress);
bufLength = Math.max(bufLength, absoluteAddress + dataLength);
buf = tmp;
}
else {
if (absoluteAddress > bufLength) {
buf.fill(EMPTY_VALUE, bufLength, absoluteAddress);
}
buf.set(dataFieldBuf, absoluteAddress);
bufLength = Math.max(bufLength, absoluteAddress + dataLength);
}
if (bufLength >= (bufferSize ?? 8192)) {
return {
data: buf.subarray(0, bufLength),
startSegmentAddress,
startLinearAddress,
};
}
break;
}
case END_OF_FILE:
if (dataLength !== 0) {
throw new Error(`Invalid EOF record on line ${lineNum}.`);
}
return {
data: buf.subarray(0, bufLength),
startSegmentAddress,
startLinearAddress,
};
case EXT_SEGMENT_ADDR:
if (dataLength !== 2 || lowAddress !== 0) {
throw new Error(`Invalid extended segment address record on line ${lineNum}.`);
}
highAddress = parseInt(dataField, 16) << 4;
break;
case START_SEGMENT_ADDR:
if (dataLength !== 4 || lowAddress !== 0) {
throw new Error(`Invalid start segment address record on line ${lineNum}.`);
}
startSegmentAddress = parseInt(dataField, 16);
break;
case EXT_LINEAR_ADDR:
if (dataLength !== 2 || lowAddress !== 0) {
throw new Error(`Invalid extended linear address record on line ${lineNum}.`);
}
highAddress = parseInt(dataField, 16) << 16;
break;
case START_LINEAR_ADDR:
if (dataLength !== 4 || lowAddress !== 0) {
throw new Error(`Invalid start linear address record on line ${lineNum}.`);
}
startLinearAddress = parseInt(dataField, 16);
break;
default:
throw new Error(`Invalid record type (${recordType}) on line ${lineNum}`);
}
if (data[pos] === "\r")
pos++;
if (data[pos] === "\n")
pos++;
}
throw new Error("Unexpected end of input: missing or invalid EOF record.");
}