const { bytesToAutoText, bytesToHex, formatHexNumber, readUint16BE, readUint16LE, readUint32BE, readUint32LE, trimTrailingNullBytes } = require('../../utils/binary-utils.js') const CODE_INFO_TLV_HEADER_BYTE_LENGTH = 2 const CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH = 1 const CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH = 0xFF const CODE_INFO_TLV = { RAW_VARIABLE: 0x01, INT8_VARIABLE: 0x02, UINT8_VARIABLE: 0x03, INT16_VARIABLE: 0x04, UINT16_VARIABLE: 0x05, INT32_VARIABLE: 0x06, UINT32_VARIABLE: 0x07, FLOAT32_VARIABLE: 0x08, DOUBLE_VARIABLE: 0x09, INT64_VARIABLE: 0x0A, UINT64_VARIABLE: 0x0B, INT128_VARIABLE: 0x0C, UINT128_VARIABLE: 0x0D, INT256_VARIABLE: 0x0E, UINT256_VARIABLE: 0x0F, RAW_ARRAY: 0x10, INT8_ARRAY: 0x11, UINT8_ARRAY: 0x12, INT16_ARRAY: 0x13, UINT16_ARRAY: 0x14, INT32_ARRAY: 0x15, UINT32_ARRAY: 0x16, FLOAT32_ARRAY: 0x17, DOUBLE_ARRAY: 0x18, INT64_ARRAY: 0x19, UINT64_ARRAY: 0x1A, INT128_ARRAY: 0x1B, UINT128_ARRAY: 0x1C, INT256_ARRAY: 0x1D, UINT256_ARRAY: 0x1E, TEXT_ARRAY: 0x1F, ENUM_VARIABLE: 0x20, STRUCT_INSTANCE: 0x21, ENUM_ARRAY: 0x22, STRUCT_ARRAY: 0x23, 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_BOARD_TLV_NAMES = { [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, ENUM: 0x02, ARRAY: 0x03 } const CODE_INFO_ENTRY_KIND_TEXT = { [CODE_INFO_ENTRY_KIND.STRUCT]: 'struct', [CODE_INFO_ENTRY_KIND.VARIABLE]: 'variable', [CODE_INFO_ENTRY_KIND.ENUM]: 'enum', [CODE_INFO_ENTRY_KIND.ARRAY]: 'array' } const CODE_INFO_ENTRY_NAME_ROLE = { DEFINITION: 'definition', INSTANCE: 'instance', VARIABLE: 'variable' } const CODE_INFO_ENTRY_TYPE_DEFINITIONS = { [CODE_INFO_TLV.RAW_VARIABLE]: { dataType: 'raw', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, name: 'raw_variable', valueTypeName: 'raw' }, [CODE_INFO_TLV.INT8_VARIABLE]: { dataType: 'int8_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 1, name: 'int8_variable', valueTypeName: 'int8_t' }, [CODE_INFO_TLV.UINT8_VARIABLE]: { dataType: 'uint8_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 1, name: 'uint8_variable', valueTypeName: 'uint8_t' }, [CODE_INFO_TLV.INT16_VARIABLE]: { dataType: 'int16_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 2, name: 'int16_variable', valueTypeName: 'int16_t' }, [CODE_INFO_TLV.UINT16_VARIABLE]: { dataType: 'uint16_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 2, name: 'uint16_variable', valueTypeName: 'uint16_t' }, [CODE_INFO_TLV.INT32_VARIABLE]: { dataType: 'int32_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 4, name: 'int32_variable', valueTypeName: 'int32_t' }, [CODE_INFO_TLV.UINT32_VARIABLE]: { dataType: 'uint32_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 4, name: 'uint32_variable', valueTypeName: 'uint32_t' }, [CODE_INFO_TLV.FLOAT32_VARIABLE]: { dataType: 'float', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 4, name: 'float32_variable', valueTypeName: 'float32' }, [CODE_INFO_TLV.DOUBLE_VARIABLE]: { dataType: 'double', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 8, name: 'double_variable', valueTypeName: 'double' }, [CODE_INFO_TLV.INT64_VARIABLE]: { dataType: 'int64_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 8, name: 'int64_variable', valueTypeName: 'int64_t' }, [CODE_INFO_TLV.UINT64_VARIABLE]: { dataType: 'uint64_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 8, name: 'uint64_variable', valueTypeName: 'uint64_t' }, [CODE_INFO_TLV.INT128_VARIABLE]: { dataType: 'int128_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 16, name: 'int128_variable', valueTypeName: 'int128_t' }, [CODE_INFO_TLV.UINT128_VARIABLE]: { dataType: 'uint128_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 16, name: 'uint128_variable', valueTypeName: 'uint128_t' }, [CODE_INFO_TLV.INT256_VARIABLE]: { dataType: 'int256_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 32, name: 'int256_variable', valueTypeName: 'int256_t' }, [CODE_INFO_TLV.UINT256_VARIABLE]: { dataType: 'uint256_t', entryKind: CODE_INFO_ENTRY_KIND.VARIABLE, expectedByteLength: 32, name: 'uint256_variable', valueTypeName: 'uint256_t' }, [CODE_INFO_TLV.ENUM_VARIABLE]: { dataType: '', entryKind: CODE_INFO_ENTRY_KIND.ENUM, name: 'enum_variable', valueTypeName: 'enum' }, [CODE_INFO_TLV.STRUCT_INSTANCE]: { dataType: 'raw', entryKind: CODE_INFO_ENTRY_KIND.STRUCT, name: 'struct_instance', valueTypeName: 'struct' }, [CODE_INFO_TLV.RAW_ARRAY]: { dataType: 'raw', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, name: 'raw_array', valueTypeName: 'raw' }, [CODE_INFO_TLV.INT8_ARRAY]: { dataType: 'int8_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 1, name: 'int8_array', valueTypeName: 'int8_t' }, [CODE_INFO_TLV.UINT8_ARRAY]: { dataType: 'uint8_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 1, name: 'uint8_array', valueTypeName: 'uint8_t' }, [CODE_INFO_TLV.INT16_ARRAY]: { dataType: 'int16_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 2, name: 'int16_array', valueTypeName: 'int16_t' }, [CODE_INFO_TLV.UINT16_ARRAY]: { dataType: 'uint16_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 2, name: 'uint16_array', valueTypeName: 'uint16_t' }, [CODE_INFO_TLV.INT32_ARRAY]: { dataType: 'int32_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 4, name: 'int32_array', valueTypeName: 'int32_t' }, [CODE_INFO_TLV.UINT32_ARRAY]: { dataType: 'uint32_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 4, name: 'uint32_array', valueTypeName: 'uint32_t' }, [CODE_INFO_TLV.FLOAT32_ARRAY]: { dataType: 'float', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 4, name: 'float32_array', valueTypeName: 'float32' }, [CODE_INFO_TLV.DOUBLE_ARRAY]: { dataType: 'double', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 8, name: 'double_array', valueTypeName: 'double' }, [CODE_INFO_TLV.INT64_ARRAY]: { dataType: 'int64_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 8, name: 'int64_array', valueTypeName: 'int64_t' }, [CODE_INFO_TLV.UINT64_ARRAY]: { dataType: 'uint64_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 8, name: 'uint64_array', valueTypeName: 'uint64_t' }, [CODE_INFO_TLV.INT128_ARRAY]: { dataType: 'int128_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 16, name: 'int128_array', valueTypeName: 'int128_t' }, [CODE_INFO_TLV.UINT128_ARRAY]: { dataType: 'uint128_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 16, name: 'uint128_array', valueTypeName: 'uint128_t' }, [CODE_INFO_TLV.INT256_ARRAY]: { dataType: 'int256_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 32, name: 'int256_array', valueTypeName: 'int256_t' }, [CODE_INFO_TLV.UINT256_ARRAY]: { dataType: 'uint256_t', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 32, name: 'uint256_array', valueTypeName: 'uint256_t' }, [CODE_INFO_TLV.TEXT_ARRAY]: { dataType: 'text', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, expectedByteLength: 1, name: 'text_array', valueTypeName: 'text' }, [CODE_INFO_TLV.ENUM_ARRAY]: { dataType: '', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, name: 'enum_array', valueTypeName: 'enum' }, [CODE_INFO_TLV.STRUCT_ARRAY]: { dataType: 'raw', entryKind: CODE_INFO_ENTRY_KIND.ARRAY, name: 'struct_array', valueTypeName: 'struct' } } const CODE_INFO_MEMORY_AREA = { DATA: 0x01, IDATA: 0x02, XDATA: 0x03, CODE: 0x04, ADDR32: 0x07 } const CODE_INFO_MEMORY_AREA_NAMES = { [CODE_INFO_MEMORY_AREA.DATA]: 'DATA', [CODE_INFO_MEMORY_AREA.IDATA]: 'IDATA', [CODE_INFO_MEMORY_AREA.XDATA]: 'XDATA', [CODE_INFO_MEMORY_AREA.CODE]: 'CODE', [CODE_INFO_MEMORY_AREA.ADDR32]: 'ADDR32' } const MEMORY_ENDIAN = { BIG: 'big', LITTLE: 'little' } function toBytes(bytes) { return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF) } function normalizeMemoryEndian(value, fallback = MEMORY_ENDIAN.LITTLE) { const text = String(value || '').trim().toLowerCase() if (text === 'little' || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE if (text === 'big' || text === 'be' || text === '0') return MEMORY_ENDIAN.BIG return fallback } function applyMemoryEndian(bytes = [], memoryEndian = MEMORY_ENDIAN.LITTLE) { const source = Array.isArray(bytes) ? bytes.slice() : [] return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE ? source.reverse() : source } function readFloat(bytes, offset, memoryEndian = MEMORY_ENDIAN.LITTLE) { if (offset + 3 >= bytes.length) return 0 const buffer = new ArrayBuffer(4) const view = new DataView(buffer) const source = applyMemoryEndian(bytes.slice(offset, offset + 4), memoryEndian) for (let index = 0; index < 4; index += 1) { view.setUint8(index, source[index] || 0) } return view.getFloat32(0, false) } function readTlvText(bytes) { return bytesToAutoText(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 formatAddress(address, addressWidth = 32) { const numberValue = Math.max(0, Math.floor(Number(address) || 0)) const hexWidth = normalizeCodeInfoAddressWidth(addressWidth) === 16 ? 4 : 8 return `0x${formatHexNumber(numberValue, hexWidth)}` } function normalizeTypeName(value, fallback) { const text = String(value || '').trim() return text || fallback } function getEntryKindText(entryKind) { return CODE_INFO_ENTRY_KIND_TEXT[entryKind] || 'unknown' } function getEntryTypeDefinition(type) { return CODE_INFO_ENTRY_TYPE_DEFINITIONS[Number(type) & 0xFF] || null } function getTlvName(type) { const tlvType = Number(type) & 0xFF const entryDefinition = getEntryTypeDefinition(tlvType) if (entryDefinition && entryDefinition.name) return entryDefinition.name if (CODE_INFO_BOARD_TLV_NAMES[tlvType]) return CODE_INFO_BOARD_TLV_NAMES[tlvType] return `tlv_0x${formatHexNumber(tlvType, 2)}` } function readMemoryEndianUint16(bytes, memoryEndian) { return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE ? readUint16LE(bytes, 0) : readUint16BE(bytes, 0) } function readMemoryEndianUint32(bytes, memoryEndian) { return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE ? readUint32LE(bytes, 0) : readUint32BE(bytes, 0) } function readNameField(valueBytes, offset, role) { if (offset >= valueBytes.length) { throw new Error('CodeInfo 内存入口 TLV 名称字段缺失') } const byteLength = valueBytes[offset] & 0xFF const nameOffset = offset + CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH const nextOffset = nameOffset + byteLength if (byteLength > CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH || nextOffset > valueBytes.length) { throw new Error(`CodeInfo 内存入口 TLV 名称长度无效,声明 ${byteLength} 字节,实际 ${Math.max(0, valueBytes.length - nameOffset)} 字节`) } return { field: { byteLength, role, text: readTlvText(valueBytes.slice(nameOffset, nextOffset)) }, nextOffset } } function ensureNoTrailingBytes(valueBytes, offset) { if (offset !== valueBytes.length) { throw new Error('CodeInfo 内存入口 TLV 长度与字段定义不一致') } } 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 parseEntryPrefix(valueBytes, options = {}) { const addressWidth = normalizeCodeInfoAddressWidth(options.addressWidth) const memoryEndian = normalizeMemoryEndian(options.memoryEndian) const addressByteLength = addressWidth === 16 ? 2 : 4 const minPrefixByteLength = (addressWidth === 16 ? 1 : 0) + addressByteLength + 2 if (valueBytes.length < minPrefixByteLength) { throw new Error(`CodeInfo 内存入口 TLV 长度无效,至少需要 ${minPrefixByteLength} 字节`) } let cursor = 0 let memoryArea = 'ADDR32' let memoryAreaCode = CODE_INFO_MEMORY_AREA.ADDR32 if (addressWidth === 16) { memoryAreaCode = valueBytes[cursor] & 0xFF memoryArea = CODE_INFO_MEMORY_AREA_NAMES[memoryAreaCode] || '' if (!memoryArea || memoryAreaCode === CODE_INFO_MEMORY_AREA.ADDR32) { throw new Error('CodeInfo 16 位地址入口存储区域无效') } cursor += 1 } const byteAddr = addressByteLength === 2 ? readMemoryEndianUint16(valueBytes.slice(cursor, cursor + addressByteLength), memoryEndian) : readMemoryEndianUint32(valueBytes.slice(cursor, cursor + addressByteLength), memoryEndian) cursor += addressByteLength const byteLength = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian) cursor += 2 return { addressByteLength, addressWidth, byteAddr, byteLength, cursor, memoryArea, memoryAreaCode } } function getEntryFallbackName(entryKindText, index) { if (entryKindText === 'enum') return `enum_${index + 1}` if (entryKindText === 'array') return `array_${index + 1}` if (entryKindText === 'variable') return `var_${index + 1}` return `struct_${index + 1}` } function createEntryBase(prefix, index, type, entryKind, names, extra = {}) { const entryKindText = getEntryKindText(entryKind) const fallbackName = getEntryFallbackName(entryKindText, index) const definitionField = names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.DEFINITION) const instanceField = names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.INSTANCE) || names.find((name) => name.role === CODE_INFO_ENTRY_NAME_ROLE.VARIABLE) const definitionName = normalizeTypeName(definitionField && definitionField.text, '') const instanceName = normalizeTypeName(instanceField && instanceField.text, definitionName || fallbackName) const symbolName = entryKind === CODE_INFO_ENTRY_KIND.VARIABLE ? normalizeTypeName(instanceField && instanceField.text, fallbackName) : instanceName const typeName = definitionName || extra.valueTypeName || symbolName return { addressByteLength: prefix.addressByteLength, addressWidth: prefix.addressWidth, byteAddr: prefix.byteAddr, byteLength: prefix.byteLength, definitionName, entryKind, entryKindText, index, instanceName, isArrayEntry: entryKind === CODE_INFO_ENTRY_KIND.ARRAY, isEnumEntry: entryKind === CODE_INFO_ENTRY_KIND.ENUM, isStructEntry: entryKind === CODE_INFO_ENTRY_KIND.STRUCT, isVariableEntry: entryKind === CODE_INFO_ENTRY_KIND.VARIABLE, memType: prefix.memoryAreaCode, memoryArea: prefix.memoryArea || 'UNKNOWN', nameByteLength: names.reduce((total, name) => total + Number(name.byteLength || 0), 0), nameFields: names.map((name) => ({ byteLength: name.byteLength, role: name.role, text: name.text })), rawByteLength: 0, sourceAddressText: formatAddress(prefix.byteAddr, prefix.addressWidth), symbolName, tlvType: Number(type) & 0xFF, typeName, ...extra } } function validateEntryByteLength(prefix, entryDefinition, label) { const expectedByteLength = Number(entryDefinition && entryDefinition.expectedByteLength) if (!Number.isFinite(expectedByteLength) || expectedByteLength <= 0) return if (Number(prefix.byteLength) !== expectedByteLength) { throw new Error(`CodeInfo ${label} 字节长度与 TYPE 定义不一致`) } } function parseVariableEntry(valueBytes, index, type, entryDefinition, options = {}) { const prefix = parseEntryPrefix(valueBytes, options) let cursor = prefix.cursor const nameResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.VARIABLE) cursor = nameResult.nextOffset ensureNoTrailingBytes(valueBytes, cursor) validateEntryByteLength(prefix, entryDefinition, '变量入口') const entry = createEntryBase( prefix, index, type, CODE_INFO_ENTRY_KIND.VARIABLE, [nameResult.field], { dataType: entryDefinition.dataType || 'raw', valueTypeName: entryDefinition.valueTypeName || 'raw' } ) return { ...entry, rawByteLength: valueBytes.length } } function parseEnumEntry(valueBytes, index, type, entryDefinition, options = {}) { const prefix = parseEntryPrefix(valueBytes, options) let cursor = prefix.cursor const definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION) cursor = definitionResult.nextOffset const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE) cursor = instanceResult.nextOffset ensureNoTrailingBytes(valueBytes, cursor) const entry = createEntryBase( prefix, index, type, CODE_INFO_ENTRY_KIND.ENUM, [definitionResult.field, instanceResult.field], { dataType: entryDefinition.dataType || getIntegerDataTypeForByteLength(prefix.byteLength), valueTypeName: entryDefinition.valueTypeName || 'enum' } ) return { ...entry, rawByteLength: valueBytes.length } } function parseStructEntry(valueBytes, index, type, entryDefinition, options = {}) { const prefix = parseEntryPrefix(valueBytes, options) let cursor = prefix.cursor const definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION) cursor = definitionResult.nextOffset const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE) cursor = instanceResult.nextOffset ensureNoTrailingBytes(valueBytes, cursor) const entry = createEntryBase( prefix, index, type, CODE_INFO_ENTRY_KIND.STRUCT, [definitionResult.field, instanceResult.field], { dataType: entryDefinition.dataType || 'raw', valueTypeName: entryDefinition.valueTypeName || 'struct' } ) return { ...entry, rawByteLength: valueBytes.length } } function readArrayDimensions(valueBytes, cursor, dimCount, memoryEndian) { const dimensions = [] for (let index = 0; index < dimCount; index += 1) { if (cursor + 2 > valueBytes.length) { throw new Error('CodeInfo 数组维度字段长度无效') } const length = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian) if (!length) throw new Error('CodeInfo 数组维度长度必须大于 0') dimensions.push(length) cursor += 2 } return { dimensions, nextOffset: cursor } } function parseArrayEntry(valueBytes, index, type, entryDefinition, options = {}) { const prefix = parseEntryPrefix(valueBytes, options) const memoryEndian = normalizeMemoryEndian(options.memoryEndian) let cursor = prefix.cursor if (cursor + 5 > valueBytes.length) { throw new Error('CodeInfo 数组入口长度无效') } const elementByteLength = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian) cursor += 2 const elementCount = readMemoryEndianUint16(valueBytes.slice(cursor, cursor + 2), memoryEndian) cursor += 2 const dimensionCount = valueBytes[cursor] & 0xFF cursor += 1 if (!elementByteLength) throw new Error('CodeInfo 数组元素字节长度必须大于 0') if (!elementCount) throw new Error('CodeInfo 数组元素数量必须大于 0') if (!dimensionCount) throw new Error('CodeInfo 数组维度数量必须大于 0') const dimensionsResult = readArrayDimensions(valueBytes, cursor, dimensionCount, memoryEndian) cursor = dimensionsResult.nextOffset const needsDefinitionName = type === CODE_INFO_TLV.ENUM_ARRAY || type === CODE_INFO_TLV.STRUCT_ARRAY let definitionResult = null if (needsDefinitionName) { definitionResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.DEFINITION) cursor = definitionResult.nextOffset } const instanceResult = readNameField(valueBytes, cursor, CODE_INFO_ENTRY_NAME_ROLE.INSTANCE) cursor = instanceResult.nextOffset ensureNoTrailingBytes(valueBytes, cursor) validateEntryByteLength({ byteLength: elementByteLength }, entryDefinition, '数组元素') const dimensionProduct = dimensionsResult.dimensions.reduce((total, value) => total * value, 1) if (dimensionProduct !== elementCount) { throw new Error('CodeInfo 数组维度乘积与元素数量不一致') } if (elementByteLength * elementCount !== prefix.byteLength) { throw new Error('CodeInfo 数组总字节长度与元素数量不一致') } const entry = createEntryBase( prefix, index, type, CODE_INFO_ENTRY_KIND.ARRAY, (definitionResult ? [definitionResult.field] : []).concat(instanceResult.field), { arrayDimensions: dimensionsResult.dimensions, arrayElementBaseIndex: 0, dataType: entryDefinition.dataType || 'raw', elementByteLength, elementCount, isEnumArrayEntry: type === CODE_INFO_TLV.ENUM_ARRAY, isStructArrayEntry: type === CODE_INFO_TLV.STRUCT_ARRAY, isTextArrayEntry: type === CODE_INFO_TLV.TEXT_ARRAY, valueTypeName: entryDefinition.valueTypeName || 'raw' } ) return { ...entry, rawByteLength: valueBytes.length } } function parseMemoryEntry(valueBytes, index, type, options = {}) { const entryDefinition = getEntryTypeDefinition(type) if (!entryDefinition) return null switch (entryDefinition.entryKind) { case CODE_INFO_ENTRY_KIND.VARIABLE: return parseVariableEntry(valueBytes, index, type, entryDefinition, options) case CODE_INFO_ENTRY_KIND.ENUM: return parseEnumEntry(valueBytes, index, type, entryDefinition, options) case CODE_INFO_ENTRY_KIND.STRUCT: return parseStructEntry(valueBytes, index, type, entryDefinition, options) case CODE_INFO_ENTRY_KIND.ARRAY: return parseArrayEntry(valueBytes, index, type, entryDefinition, options) default: return null } } function applyCodeInfoTlvValue(target, type, valueBytes, options = {}) { const memoryEndian = normalizeMemoryEndian(options.memoryEndian) 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 = readMemoryEndianUint16(valueBytes, memoryEndian) break case CODE_INFO_TLV.BUS_DIV: if (valueBytes.length < 4) throw new Error('CodeInfo bus_div TLV 长度无效') target.busDiv = readFloat(valueBytes, 0, memoryEndian) break case CODE_INFO_TLV.ALONG_DIV: if (valueBytes.length < 4) throw new Error('CodeInfo along_div TLV 长度无效') target.alongDiv = readFloat(valueBytes, 0, memoryEndian) break case CODE_INFO_TLV.CHIP_MODEL: target.chipModel = readTlvText(valueBytes) break case CODE_INFO_TLV.MODEL: target.model = readTlvText(valueBytes) break default: break } } function parseCodeInfoTlvs(bytes, options = {}) { 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: getTlvName(type), rawHex: bytesToHex(valueBytes, ' '), type } info.tlvItems.push(item) const entry = parseMemoryEntry(valueBytes, info.entries.length, type, options) if (entry) { info.entries.push(entry) } else { applyCodeInfoTlvValue(info, type, valueBytes, options) } 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 memoryEndian = normalizeMemoryEndian(options.memoryEndian) const tlvInfo = parseCodeInfoTlvs(source, { addressWidth, memoryEndian }) const entries = tlvInfo.entries const entryCount = entries.length const structCount = entries.filter((entry) => entry.isStructEntry || entry.isStructArrayEntry).length const enumCount = entries.filter((entry) => entry.isEnumEntry || entry.isEnumArrayEntry).length const variableCount = entries.filter((entry) => entry.isVariableEntry).length const arrayCount = entries.filter((entry) => entry.isArrayEntry).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, arrayCount, byteLength: codeInfoByteLength, entryCount, enumCount, maxPacketLength, memoryEndian, memoryEndianRaw: Number(options.memoryEndianRaw || options.memoryEndianMark || 0) & 0xFFFF, rawHex: bytesToHex(source, ' '), structEntryAddressByteLength: entryAddressByteLengths.length === 1 ? entryAddressByteLengths[0] : 0, structCount, structTable: entries, tableByteLength: entries.reduce((total, entry) => total + Number(entry.rawByteLength || 0), 0), tableCapacity: entryCount, tableRemainderByteLength: 0, tlvByteLength: source.length, truncated: false, variableCount } } function getIntegerDataTypeForByteLength(byteLength) { const length = Number(byteLength) if (length === 1) return 'uint8_t' if (length === 2) return 'uint16_t' if (length === 4) return 'uint32_t' return 'raw' } function getArrayIndexPath(linearIndex, dimensions = []) { const safeDimensions = (Array.isArray(dimensions) ? dimensions : []) .map((value) => Math.max(1, Math.floor(Number(value) || 1))) .filter((value) => value > 0) if (!safeDimensions.length) return [Math.max(0, Math.floor(Number(linearIndex) || 0))] const indexes = Array.from({ length: safeDimensions.length }, () => 0) let remaining = Math.max(0, Math.floor(Number(linearIndex) || 0)) for (let index = safeDimensions.length - 1; index >= 0; index -= 1) { indexes[index] = remaining % safeDimensions[index] remaining = Math.floor(remaining / safeDimensions[index]) } return indexes } function formatArrayIndexPath(indexPath = []) { return (Array.isArray(indexPath) ? indexPath : [indexPath]) .map((index) => `[${Math.max(0, Math.floor(Number(index) || 0))}]`) .join('') } function createRegisterSourceMeta(entry, byteStart, byteLength, extra = {}) { const sourceAddress = entry.byteAddr + Math.max(0, Math.floor(Number(byteStart) || 0)) return { sourceAddress, sourceAddressByteLength: entry.addressByteLength, sourceAddressText: formatAddress(sourceAddress, entry.addressWidth), sourceAddressWidth: entry.addressWidth, sourceArrayDimensions: entry.arrayDimensions, sourceByteLength: byteLength, sourceDefinitionName: entry.definitionName, sourceElementByteLength: entry.elementByteLength, sourceElementCount: entry.elementCount, sourceElementType: entry.valueTypeName, sourceEntryKind: entry.entryKindText, sourceInstanceName: entry.instanceName, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.symbolName, sourceSymbolType: entry.typeName, sourceValueType: entry.valueTypeName, ...extra } } 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'), ...createRegisterSourceMeta(entry, offset, 1) }) } return registers } function createScalarRegister(entry) { const dataType = entry.dataType || (entry.isEnumEntry ? getIntegerDataTypeForByteLength(entry.byteLength) : 'raw') const register = { byteStart: 0, dataType, name: entry.symbolName, ...createRegisterSourceMeta(entry, 0, entry.byteLength) } if (dataType === 'text') { register.textByteLength = String(entry.byteLength) } return [register] } function createArrayRegisters(entry) { if (entry.isStructArrayEntry) return createRegistersForByteSpan(entry) const dataType = entry.dataType || 'raw' if (dataType === 'text') { return [{ byteStart: 0, dataType, name: entry.symbolName, textByteLength: String(entry.byteLength), ...createRegisterSourceMeta(entry, 0, entry.byteLength) }] } const count = Math.max(1, Number(entry.elementCount) || 1) const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1) const baseIndex = Math.max(0, Number(entry.arrayElementBaseIndex) || 0) const registers = [] for (let index = 0; index < count; index += 1) { const linearIndex = baseIndex + index const indexPath = getArrayIndexPath(linearIndex, entry.arrayDimensions) const byteStart = index * elementByteLength registers.push({ byteStart, dataType, name: `${entry.symbolName}${formatArrayIndexPath(indexPath)}`, ...createRegisterSourceMeta(entry, byteStart, elementByteLength, { sourceArrayIndex: linearIndex, sourceArrayIndexPath: indexPath, sourceSymbolName: `${entry.symbolName}${formatArrayIndexPath(indexPath)}` }) }) } return registers } function createCodeInfoContext(codeInfo = {}) { return { addressWidth: codeInfo.addressWidth, codeInfoAddressWidth: codeInfo.addressWidth, maxPacketLength: codeInfo.maxPacketLength, memoryEndian: codeInfo.memoryEndian, memoryEndianRaw: Number(codeInfo.memoryEndianRaw || 0) & 0xFFFF, storageAddressWidth: codeInfo.addressWidth } } function createSourceMeta(entry, byteLength) { return { addressUnit: 'byte', sourceAddress: entry.byteAddr, sourceAddressByteLength: entry.addressByteLength, sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth), sourceAddressWidth: entry.addressWidth, sourceArrayDimensions: entry.arrayDimensions, sourceByteLength: byteLength, sourceDefinitionName: entry.definitionName, sourceElementByteLength: entry.elementByteLength, sourceElementCount: entry.elementCount, sourceElementType: entry.valueTypeName, sourceEntryKind: entry.entryKindText, sourceInstanceName: entry.instanceName, sourceMemoryArea: entry.memoryArea, sourceMemoryClass: entry.memoryArea, sourceSymbolName: entry.symbolName, sourceSymbolType: entry.typeName, sourceValueType: entry.valueTypeName } } function createGroupFromEntry(entry, chunkLength, suffix = '', codeInfo = {}) { const isStructLike = entry.isStructEntry || entry.isStructArrayEntry const registers = isStructLike ? createRegistersForByteSpan(entry) : (entry.isArrayEntry ? createArrayRegisters(entry) : createScalarRegister(entry)) return { codeInfoContext: createCodeInfoContext(codeInfo), layout: isStructLike ? 'struct' : 'register', name: `${entry.symbolName || 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 createEntryChunk(entry, offset, chunkLength) { const chunkEntry = { ...entry, byteAddr: entry.byteAddr + offset, byteLength: chunkLength } if (entry.isArrayEntry) { const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1) const elementOffset = Math.floor(offset / elementByteLength) const elementCount = Math.max(1, Math.floor(chunkLength / elementByteLength)) chunkEntry.arrayElementBaseIndex = (Number(entry.arrayElementBaseIndex) || 0) + elementOffset chunkEntry.elementCount = elementCount } return chunkEntry } function getChunkStep(entry, maxRegisters) { if (!entry.isArrayEntry) return maxRegisters const elementByteLength = Math.max(1, Number(entry.elementByteLength) || 1) const elementsPerChunk = Math.max(1, Math.floor(maxRegisters / elementByteLength)) return elementsPerChunk * elementByteLength } 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.isEnumEntry && !entry.isVariableEntry && !entry.isArrayEntry) return const chunkStep = getChunkStep(entry, maxRegisters) for (let offset = 0; offset < entry.byteLength; offset += chunkStep) { const chunkLength = Math.min(chunkStep, entry.byteLength - offset) const chunkEntry = createEntryChunk(entry, offset, chunkLength) const suffix = entry.byteLength > chunkStep ? ` #${Math.floor(offset / chunkStep) + 1}` : '' groups.push(createGroupFromEntry(chunkEntry, chunkLength, suffix, codeInfo)) } }) return groups } module.exports = { CODE_INFO_ENTRY_KIND, CODE_INFO_ENTRY_MAX_NAME_BYTE_LENGTH, CODE_INFO_ENTRY_NAME_LENGTH_BYTE_LENGTH, CODE_INFO_MEMORY_AREA, CODE_INFO_TLV_HEADER_BYTE_LENGTH, CODE_INFO_TLV, MEMORY_ENDIAN, createGroupsFromCodeInfo, normalizeCodeInfoAddressWidth, parseCodeInfo }