const { bytesToHex, bytesToUtf8Text, trimTrailingNullBytes } = require('../../utils/binary-utils.js') const CODE_INFO_TLV_HEADER_BYTE_LENGTH = 2 const CODE_INFO_ENTRY_NAME_BYTE_LENGTH = 32 const CODE_INFO_TLV = { DATA_STRUCT: 0x01, DATA_VARIABLE: 0x02, IDATA_STRUCT: 0x03, IDATA_VARIABLE: 0x04, XDATA_STRUCT: 0x05, XDATA_VARIABLE: 0x06, ADDR32_STRUCT: 0x07, ADDR32_VARIABLE: 0x08, CAVE_FREQ: 0x40, REF_VOLT: 0x41, AMP_GAIN: 0x42, RS_SHUNT: 0x43, BUS_DIV: 0x44, ALONG_DIV: 0x45, CHIP_MODEL: 0x46, MODEL: 0x47 } const CODE_INFO_TLV_NAMES = { [CODE_INFO_TLV.DATA_STRUCT]: 'data_struct', [CODE_INFO_TLV.DATA_VARIABLE]: 'data_var', [CODE_INFO_TLV.IDATA_STRUCT]: 'idata_struct', [CODE_INFO_TLV.IDATA_VARIABLE]: 'idata_var', [CODE_INFO_TLV.XDATA_STRUCT]: 'xdata_struct', [CODE_INFO_TLV.XDATA_VARIABLE]: 'xdata_var', [CODE_INFO_TLV.ADDR32_STRUCT]: 'addr32_struct', [CODE_INFO_TLV.ADDR32_VARIABLE]: 'addr32_var', [CODE_INFO_TLV.CAVE_FREQ]: 'cave_freq', [CODE_INFO_TLV.REF_VOLT]: 'ref_volt', [CODE_INFO_TLV.AMP_GAIN]: 'amp_gain', [CODE_INFO_TLV.RS_SHUNT]: 'rs_shunt', [CODE_INFO_TLV.BUS_DIV]: 'bus_div', [CODE_INFO_TLV.ALONG_DIV]: 'along_div', [CODE_INFO_TLV.CHIP_MODEL]: 'chip_model', [CODE_INFO_TLV.MODEL]: 'model' } const CODE_INFO_ENTRY_KIND = { STRUCT: 0x00, VARIABLE: 0x01 } const CODE_INFO_ENTRY_KIND_TEXT = { [CODE_INFO_ENTRY_KIND.STRUCT]: 'struct', [CODE_INFO_ENTRY_KIND.VARIABLE]: 'variable' } const CODE_INFO_ENTRY_LAYOUTS = { [CODE_INFO_TLV.DATA_STRUCT]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.STRUCT, memoryArea: 'DATA' }, [CODE_INFO_TLV.DATA_VARIABLE]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, memoryArea: 'DATA' }, [CODE_INFO_TLV.IDATA_STRUCT]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.STRUCT, memoryArea: 'IDATA' }, [CODE_INFO_TLV.IDATA_VARIABLE]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, memoryArea: 'IDATA' }, [CODE_INFO_TLV.XDATA_STRUCT]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.STRUCT, memoryArea: 'XDATA' }, [CODE_INFO_TLV.XDATA_VARIABLE]: { addressWidth: 16, entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, memoryArea: 'XDATA' }, [CODE_INFO_TLV.ADDR32_STRUCT]: { addressWidth: 32, entryKind: CODE_INFO_ENTRY_KIND.STRUCT, memoryArea: 'ADDR32' }, [CODE_INFO_TLV.ADDR32_VARIABLE]: { addressWidth: 32, entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, memoryArea: 'ADDR32' } } const MEMORY_ENDIAN = { BIG: 'big', LITTLE: 'little' } function toBytes(bytes) { return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF) } function readUint16(bytes, offset) { if (offset + 1 >= bytes.length) return 0 return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF } function readUint32(bytes, offset) { if (offset + 3 >= bytes.length) return 0 return ( ((bytes[offset] || 0) * 0x1000000) + (((bytes[offset + 1] || 0) << 16) >>> 0) + (((bytes[offset + 2] || 0) << 8) >>> 0) + (bytes[offset + 3] || 0) ) >>> 0 } function readFloat(bytes, offset) { if (offset + 3 >= bytes.length) return 0 const buffer = new ArrayBuffer(4) const view = new DataView(buffer) for (let index = 0; index < 4; index += 1) { view.setUint8(index, bytes[offset + index] || 0) } return view.getFloat32(0, false) } function readTlvText(bytes) { return bytesToUtf8Text(trimTrailingNullBytes(bytes)).trim() } function normalizeCodeInfoAddressWidth(value) { const numberValue = Number(value) if (numberValue === 16) return 16 if (numberValue === 32) return 32 throw new Error('CodeInfo 描述符地址长度必须为 16 或 32') } function getEntryLayout(type) { const entryType = Number(type) & 0xFF const layout = CODE_INFO_ENTRY_LAYOUTS[entryType] if (!layout) return null const addressWidth = layout.addressWidth const addressByteLength = addressWidth === 16 ? 2 : 4 const valueByteLength = addressByteLength + 2 + CODE_INFO_ENTRY_NAME_BYTE_LENGTH return { addressByteLength, addressWidth, entryKind: layout.entryKind, hasMemoryType: false, memoryArea: layout.memoryArea, nameByteLength: CODE_INFO_ENTRY_NAME_BYTE_LENGTH, nameOffset: addressByteLength + 2, valueByteLength } } function formatAddress(address, addressWidth = 32) { const numberValue = Math.max(0, Math.floor(Number(address) || 0)) const hexWidth = normalizeCodeInfoAddressWidth(addressWidth) === 16 ? 4 : 8 return `0x${numberValue.toString(16).toUpperCase().padStart(hexWidth, '0')}` } function normalizeTypeName(value, fallback) { const text = String(value || '').trim() return text || fallback } function getEntryKindText(entryKind) { return CODE_INFO_ENTRY_KIND_TEXT[entryKind] || 'unknown' } function normalizeMemoryEndian(value) { const text = String(value || '').trim().toLowerCase() if (text === 'little' || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE return MEMORY_ENDIAN.BIG } function resolveCodeInfoByteLength(sourceLength, options = {}) { const expectedLength = Number( options.codeInfoByteLength === undefined ? options.byteLength : options.codeInfoByteLength ) if (!Number.isFinite(expectedLength) || expectedLength <= 0) return sourceLength if (sourceLength < expectedLength) { throw new Error('CodeInfo 数据长度小于描述符声明长度') } return Math.floor(expectedLength) } function parseMemoryEntry(valueBytes, index, type) { const layout = getEntryLayout(type) if (!layout) return null if (valueBytes.length !== layout.valueByteLength) { throw new Error(`CodeInfo 内存入口 TLV 长度无效,期望 ${layout.valueByteLength} 字节`) } const memType = 0 const addressOffset = 0 const byteAddr = layout.addressByteLength === 2 ? readUint16(valueBytes, addressOffset) : readUint32(valueBytes, addressOffset) const byteLength = readUint16(valueBytes, addressOffset + layout.addressByteLength) const nameOffset = layout.nameOffset const entryKind = layout.entryKind const entryKindText = getEntryKindText(entryKind) const typeName = normalizeTypeName( readTlvText(valueBytes.slice(nameOffset, nameOffset + layout.nameByteLength)), `${entryKindText === 'variable' ? 'var' : 'struct'}_${index + 1}` ) const memoryArea = layout.memoryArea || 'UNKNOWN' return { addressByteLength: layout.addressByteLength, addressWidth: layout.addressWidth, byteAddr, byteLength, entryKind, entryKindText, index, isStructEntry: entryKind === CODE_INFO_ENTRY_KIND.STRUCT, isVariableEntry: entryKind === CODE_INFO_ENTRY_KIND.VARIABLE, memType, memoryArea, rawByteLength: valueBytes.length, sourceAddressText: formatAddress(byteAddr, layout.addressWidth), tlvType: Number(type) & 0xFF, typeName } } function applyCodeInfoTlvValue(target, type, valueBytes) { switch (type) { case CODE_INFO_TLV.CAVE_FREQ: if (valueBytes.length < 1) throw new Error('CodeInfo cave_freq TLV 长度无效') target.caveFreq = valueBytes[0] & 0xFF break case CODE_INFO_TLV.REF_VOLT: if (valueBytes.length < 1) throw new Error('CodeInfo ref_volt TLV 长度无效') target.refVoltRaw = valueBytes[0] & 0xFF target.refVolt = target.refVoltRaw / 10 break case CODE_INFO_TLV.AMP_GAIN: if (valueBytes.length < 1) throw new Error('CodeInfo amp_gain TLV 长度无效') target.ampGain = valueBytes[0] & 0xFF break case CODE_INFO_TLV.RS_SHUNT: if (valueBytes.length < 2) throw new Error('CodeInfo rs_shunt TLV 长度无效') target.rsShunt = readUint16(valueBytes, 0) break case CODE_INFO_TLV.BUS_DIV: if (valueBytes.length < 4) throw new Error('CodeInfo bus_div TLV 长度无效') target.busDiv = readFloat(valueBytes, 0) break case CODE_INFO_TLV.ALONG_DIV: if (valueBytes.length < 4) throw new Error('CodeInfo along_div TLV 长度无效') target.alongDiv = readFloat(valueBytes, 0) break case CODE_INFO_TLV.CHIP_MODEL: target.chipModel = readTlvText(valueBytes) break case CODE_INFO_TLV.MODEL: target.model = readTlvText(valueBytes) break default: if (type >= 0x20 && type < 0x40) { target.customTlvCount = (Number(target.customTlvCount) || 0) + 1 } break } } function parseCodeInfoTlvs(bytes) { const info = { entries: [], tlvItems: [] } let offset = 0 while (offset < bytes.length) { if (offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH > bytes.length) { throw new Error('CodeInfo TLV 项头长度无效') } const type = bytes[offset] & 0xFF const byteLength = bytes[offset + 1] & 0xFF const valueOffset = offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH const nextOffset = valueOffset + byteLength if (nextOffset > bytes.length) { throw new Error('CodeInfo TLV 项长度超出声明范围') } const valueBytes = bytes.slice(valueOffset, nextOffset) const item = { byteLength, name: CODE_INFO_TLV_NAMES[type] || `tlv_0x${type.toString(16).toUpperCase().padStart(2, '0')}`, rawHex: bytesToHex(valueBytes, ' '), type } info.tlvItems.push(item) const entry = parseMemoryEntry(valueBytes, info.entries.length, type) if (entry) { info.entries.push(entry) } else { applyCodeInfoTlvValue(info, type, valueBytes) } offset = nextOffset } return info } function parseCodeInfo(bytes, options = {}) { const inputBytes = toBytes(bytes) const codeInfoByteLength = resolveCodeInfoByteLength(inputBytes.length, options) const source = inputBytes.slice(0, codeInfoByteLength) const addressWidth = normalizeCodeInfoAddressWidth(options.addressWidth) const maxPacketLength = Number(options.maxPacketLength || 0) & 0xFFFF const tlvInfo = parseCodeInfoTlvs(source) const entries = tlvInfo.entries const entryCount = entries.length const entryAddressByteLengths = entries .map((entry) => Number(entry.addressByteLength) || 0) .filter((value, index, list) => value > 0 && list.indexOf(value) === index) return { ...tlvInfo, addressWidth, addressWidthRaw: addressWidth, byteLength: codeInfoByteLength, entryCount, maxPacketLength, memoryEndian: normalizeMemoryEndian(options.memoryEndian), memoryEndianRaw: Number(options.memoryEndianRaw || options.memoryEndianMark || 0) & 0xFFFF, rawHex: bytesToHex(source, ' '), structEntryAddressByteLength: entryAddressByteLengths.length === 1 ? entryAddressByteLengths[0] : 0, structCount: entryCount, structTable: entries, tableByteLength: entries.reduce((total, entry) => total + Number(entry.rawByteLength || 0), 0), tableCapacity: entryCount, tableRemainderByteLength: 0, tlvByteLength: source.length, truncated: false } } function createRegistersForByteSpan(entry) { const count = Math.max(1, Number(entry.byteLength) || 1) const registers = [] for (let offset = 0; offset < count; offset += 1) { registers.push({ byteStart: offset, dataType: 'uint8_t', isPlaceholderByteField: true, name: offset.toString(16).toUpperCase().padStart(2, '0'), sourceAddress: entry.byteAddr + offset, sourceAddressByteLength: entry.addressByteLength, sourceAddressText: formatAddress(entry.byteAddr + offset, entry.addressWidth), sourceAddressWidth: entry.addressWidth, sourceByteLength: 1, sourceEntryKind: entry.entryKindText, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.typeName, sourceSymbolType: entry.typeName }) } return registers } function createVariableRegisters(entry) { return [{ byteStart: 0, dataType: 'raw', name: entry.typeName, sourceAddress: entry.byteAddr, sourceAddressByteLength: entry.addressByteLength, sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth), sourceAddressWidth: entry.addressWidth, sourceByteLength: entry.byteLength, sourceEntryKind: entry.entryKindText, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.typeName, sourceSymbolType: entry.typeName }] } function createCodeInfoContext(codeInfo = {}) { return { addressWidth: codeInfo.addressWidth, codeInfoAddressWidth: codeInfo.addressWidth, maxPacketLength: codeInfo.maxPacketLength, memoryEndian: codeInfo.memoryEndian, storageAddressWidth: codeInfo.addressWidth } } function createSourceMeta(entry, byteLength) { return { sourceAddress: entry.byteAddr, sourceAddressByteLength: entry.addressByteLength, sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth), sourceAddressWidth: entry.addressWidth, sourceByteLength: byteLength, sourceEntryKind: entry.entryKindText, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.typeName, sourceSymbolType: entry.typeName } } function createGroupFromEntry(entry, chunkLength, suffix = '', codeInfo = {}) { const isStructEntry = entry.entryKind === CODE_INFO_ENTRY_KIND.STRUCT const registers = isStructEntry ? createRegistersForByteSpan(entry) : createVariableRegisters(entry) return { addressUnit: 'byte', codeInfoContext: createCodeInfoContext(codeInfo), layout: isStructEntry ? 'struct' : 'register', name: `${entry.typeName}${suffix}`, quantity: registers.length, registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding', registers, ...createSourceMeta(entry, chunkLength), sourceSegment: 'CodeInfo TLV', sourceSegmentModule: '', startAddress: formatAddress(entry.byteAddr, entry.addressWidth) } } function createGroupsFromCodeInfo(codeInfo, options = {}) { const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256) const groups = [] const entries = Array.isArray(codeInfo.entries) ? codeInfo.entries : codeInfo.structTable ;(Array.isArray(entries) ? entries : []).forEach((entry) => { if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') return if (!entry.isStructEntry && !entry.isVariableEntry) return for (let offset = 0; offset < entry.byteLength; offset += maxRegisters) { const chunkLength = Math.min(maxRegisters, entry.byteLength - offset) const chunkEntry = { ...entry, byteAddr: entry.byteAddr + offset, byteLength: chunkLength } const suffix = entry.byteLength > maxRegisters ? ` #${Math.floor(offset / maxRegisters) + 1}` : '' groups.push(createGroupFromEntry(chunkEntry, chunkLength, suffix, codeInfo)) } }) return groups } module.exports = { CODE_INFO_ENTRY_KIND, CODE_INFO_ENTRY_NAME_BYTE_LENGTH, CODE_INFO_TLV_HEADER_BYTE_LENGTH, CODE_INFO_TLV, MEMORY_ENDIAN, createGroupsFromCodeInfo, normalizeCodeInfoAddressWidth, parseCodeInfo }