| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- 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
- }
|