code-info-parser.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. const {
  2. bytesToHex,
  3. bytesToUtf8Text,
  4. trimTrailingNullBytes
  5. } = require('../../utils/binary-utils.js')
  6. const CODE_INFO_TLV_HEADER_BYTE_LENGTH = 2
  7. const CODE_INFO_TLV = {
  8. CAVE_FREQ: 0x01,
  9. REF_VOLT: 0x02,
  10. AMP_GAIN: 0x03,
  11. RS_SHUNT: 0x04,
  12. BUS_DIV: 0x05,
  13. ALONG_DIV: 0x06,
  14. CHIP_MODEL: 0x07,
  15. MODEL: 0x08,
  16. STRUCT_ENTRY16: 0x20,
  17. VARIABLE_ENTRY16: 0x21,
  18. STRUCT_ENTRY32: 0x28,
  19. VARIABLE_ENTRY32: 0x29
  20. }
  21. const CODE_INFO_TLV_NAMES = {
  22. [CODE_INFO_TLV.CAVE_FREQ]: 'cave_freq',
  23. [CODE_INFO_TLV.REF_VOLT]: 'ref_volt',
  24. [CODE_INFO_TLV.AMP_GAIN]: 'amp_gain',
  25. [CODE_INFO_TLV.RS_SHUNT]: 'rs_shunt',
  26. [CODE_INFO_TLV.BUS_DIV]: 'bus_div',
  27. [CODE_INFO_TLV.ALONG_DIV]: 'along_div',
  28. [CODE_INFO_TLV.CHIP_MODEL]: 'chip_model',
  29. [CODE_INFO_TLV.MODEL]: 'model',
  30. [CODE_INFO_TLV.STRUCT_ENTRY16]: 'struct_entry16',
  31. [CODE_INFO_TLV.VARIABLE_ENTRY16]: 'variable_entry16',
  32. [CODE_INFO_TLV.STRUCT_ENTRY32]: 'struct_entry32',
  33. [CODE_INFO_TLV.VARIABLE_ENTRY32]: 'variable_entry32'
  34. }
  35. const CODE_INFO_ENTRY_KIND = {
  36. STRUCT: 0x00,
  37. VARIABLE: 0x01
  38. }
  39. const CODE_INFO_ENTRY_KIND_TEXT = {
  40. [CODE_INFO_ENTRY_KIND.STRUCT]: 'struct',
  41. [CODE_INFO_ENTRY_KIND.VARIABLE]: 'variable'
  42. }
  43. const MEMORY_TYPE_AREAS = {
  44. 0x01: 'DATA',
  45. 0x02: 'IDATA',
  46. 0x03: 'XDATA',
  47. 0x04: 'CODE'
  48. }
  49. const MEMORY_ENDIAN = {
  50. BIG: 'big',
  51. LITTLE: 'little'
  52. }
  53. function toBytes(bytes) {
  54. return Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  55. }
  56. function readUint16(bytes, offset) {
  57. if (offset + 1 >= bytes.length) return 0
  58. return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
  59. }
  60. function readUint32(bytes, offset) {
  61. if (offset + 3 >= bytes.length) return 0
  62. return (
  63. ((bytes[offset] || 0) * 0x1000000)
  64. + (((bytes[offset + 1] || 0) << 16) >>> 0)
  65. + (((bytes[offset + 2] || 0) << 8) >>> 0)
  66. + (bytes[offset + 3] || 0)
  67. ) >>> 0
  68. }
  69. function readFloat(bytes, offset) {
  70. if (offset + 3 >= bytes.length) return 0
  71. const buffer = new ArrayBuffer(4)
  72. const view = new DataView(buffer)
  73. for (let index = 0; index < 4; index += 1) {
  74. view.setUint8(index, bytes[offset + index] || 0)
  75. }
  76. return view.getFloat32(0, false)
  77. }
  78. function readTlvText(bytes) {
  79. return bytesToUtf8Text(trimTrailingNullBytes(bytes)).trim()
  80. }
  81. function normalizeCodeInfoAddressWidth(value) {
  82. const numberValue = Number(value)
  83. if (numberValue === 16) return 16
  84. if (numberValue === 32) return 32
  85. throw new Error('CodeInfo 描述符地址长度必须为 16 或 32')
  86. }
  87. function getEntryLayout(type) {
  88. const entryType = Number(type) & 0xFF
  89. const isStructEntry = entryType === CODE_INFO_TLV.STRUCT_ENTRY16 || entryType === CODE_INFO_TLV.STRUCT_ENTRY32
  90. const isVariableEntry = entryType === CODE_INFO_TLV.VARIABLE_ENTRY16 || entryType === CODE_INFO_TLV.VARIABLE_ENTRY32
  91. if (!isStructEntry && !isVariableEntry) return null
  92. const addressWidth = entryType === CODE_INFO_TLV.STRUCT_ENTRY32 || entryType === CODE_INFO_TLV.VARIABLE_ENTRY32
  93. ? 32
  94. : 16
  95. const addressByteLength = addressWidth === 16 ? 2 : 4
  96. return {
  97. addressByteLength,
  98. addressWidth,
  99. entryKind: isStructEntry ? CODE_INFO_ENTRY_KIND.STRUCT : CODE_INFO_ENTRY_KIND.VARIABLE,
  100. hasMemoryType: addressWidth === 16,
  101. minByteLength: addressWidth === 16 ? 5 : 6
  102. }
  103. }
  104. function formatAddress(address, addressWidth = 32) {
  105. const numberValue = Math.max(0, Math.floor(Number(address) || 0))
  106. const hexWidth = normalizeCodeInfoAddressWidth(addressWidth) === 16 ? 4 : 8
  107. return `0x${numberValue.toString(16).toUpperCase().padStart(hexWidth, '0')}`
  108. }
  109. function normalizeTypeName(value, fallback) {
  110. const text = String(value || '').trim()
  111. return text || fallback
  112. }
  113. function getEntryKindText(entryKind) {
  114. return CODE_INFO_ENTRY_KIND_TEXT[entryKind] || 'unknown'
  115. }
  116. function normalizeMemoryEndian(value) {
  117. const text = String(value || '').trim().toLowerCase()
  118. if (text === 'little' || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE
  119. return MEMORY_ENDIAN.BIG
  120. }
  121. function resolveCodeInfoByteLength(sourceLength, options = {}) {
  122. const expectedLength = Number(
  123. options.codeInfoByteLength === undefined ? options.byteLength : options.codeInfoByteLength
  124. )
  125. if (!Number.isFinite(expectedLength) || expectedLength <= 0) return sourceLength
  126. if (sourceLength < expectedLength) {
  127. throw new Error('CodeInfo 数据长度小于描述符声明长度')
  128. }
  129. return Math.floor(expectedLength)
  130. }
  131. function parseMemoryEntry(valueBytes, index, type) {
  132. const layout = getEntryLayout(type)
  133. if (!layout) return null
  134. if (valueBytes.length < layout.minByteLength) {
  135. throw new Error('CodeInfo 内存入口 TLV 长度无效')
  136. }
  137. const memType = layout.hasMemoryType ? (valueBytes[0] & 0xFF) : 0
  138. const addressOffset = layout.hasMemoryType ? 1 : 0
  139. const byteAddr = layout.addressByteLength === 2
  140. ? readUint16(valueBytes, addressOffset)
  141. : readUint32(valueBytes, addressOffset)
  142. const byteLength = readUint16(valueBytes, addressOffset + layout.addressByteLength)
  143. const nameOffset = layout.minByteLength
  144. const entryKind = layout.entryKind
  145. const entryKindText = getEntryKindText(entryKind)
  146. const nameByteLength = Math.max(0, valueBytes.length - layout.minByteLength)
  147. const typeName = normalizeTypeName(
  148. readTlvText(valueBytes.slice(nameOffset, nameOffset + nameByteLength)),
  149. `${entryKindText === 'variable' ? 'var' : 'struct'}_${index + 1}`
  150. )
  151. const memoryArea = layout.addressWidth === 32
  152. ? 'ADDR32'
  153. : (MEMORY_TYPE_AREAS[memType] || 'UNKNOWN')
  154. return {
  155. addressByteLength: layout.addressByteLength,
  156. addressWidth: layout.addressWidth,
  157. byteAddr,
  158. byteLength,
  159. entryKind,
  160. entryKindText,
  161. index,
  162. isStructEntry: entryKind === CODE_INFO_ENTRY_KIND.STRUCT,
  163. isVariableEntry: entryKind === CODE_INFO_ENTRY_KIND.VARIABLE,
  164. memType,
  165. memoryArea,
  166. rawByteLength: valueBytes.length,
  167. sourceAddressText: formatAddress(byteAddr, layout.addressWidth),
  168. tlvType: Number(type) & 0xFF,
  169. typeName
  170. }
  171. }
  172. function applyCodeInfoTlvValue(target, type, valueBytes) {
  173. switch (type) {
  174. case CODE_INFO_TLV.CAVE_FREQ:
  175. if (valueBytes.length < 1) throw new Error('CodeInfo cave_freq TLV 长度无效')
  176. target.caveFreq = valueBytes[0] & 0xFF
  177. break
  178. case CODE_INFO_TLV.REF_VOLT:
  179. if (valueBytes.length < 1) throw new Error('CodeInfo ref_volt TLV 长度无效')
  180. target.refVoltRaw = valueBytes[0] & 0xFF
  181. target.refVolt = target.refVoltRaw / 10
  182. break
  183. case CODE_INFO_TLV.AMP_GAIN:
  184. if (valueBytes.length < 1) throw new Error('CodeInfo amp_gain TLV 长度无效')
  185. target.ampGain = valueBytes[0] & 0xFF
  186. break
  187. case CODE_INFO_TLV.RS_SHUNT:
  188. if (valueBytes.length < 2) throw new Error('CodeInfo rs_shunt TLV 长度无效')
  189. target.rsShunt = readUint16(valueBytes, 0)
  190. break
  191. case CODE_INFO_TLV.BUS_DIV:
  192. if (valueBytes.length < 4) throw new Error('CodeInfo bus_div TLV 长度无效')
  193. target.busDiv = readFloat(valueBytes, 0)
  194. break
  195. case CODE_INFO_TLV.ALONG_DIV:
  196. if (valueBytes.length < 4) throw new Error('CodeInfo along_div TLV 长度无效')
  197. target.alongDiv = readFloat(valueBytes, 0)
  198. break
  199. case CODE_INFO_TLV.CHIP_MODEL:
  200. target.chipModel = readTlvText(valueBytes)
  201. break
  202. case CODE_INFO_TLV.MODEL:
  203. target.model = readTlvText(valueBytes)
  204. break
  205. default:
  206. break
  207. }
  208. }
  209. function parseCodeInfoTlvs(bytes) {
  210. const info = {
  211. entries: [],
  212. tlvItems: []
  213. }
  214. let offset = 0
  215. while (offset < bytes.length) {
  216. if (offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH > bytes.length) {
  217. throw new Error('CodeInfo TLV 项头长度无效')
  218. }
  219. const type = bytes[offset] & 0xFF
  220. const byteLength = bytes[offset + 1] & 0xFF
  221. const valueOffset = offset + CODE_INFO_TLV_HEADER_BYTE_LENGTH
  222. const nextOffset = valueOffset + byteLength
  223. if (nextOffset > bytes.length) {
  224. throw new Error('CodeInfo TLV 项长度超出声明范围')
  225. }
  226. const valueBytes = bytes.slice(valueOffset, nextOffset)
  227. const item = {
  228. byteLength,
  229. name: CODE_INFO_TLV_NAMES[type] || `tlv_0x${type.toString(16).toUpperCase().padStart(2, '0')}`,
  230. rawHex: bytesToHex(valueBytes, ' '),
  231. type
  232. }
  233. info.tlvItems.push(item)
  234. const entry = parseMemoryEntry(valueBytes, info.entries.length, type)
  235. if (entry) {
  236. info.entries.push(entry)
  237. } else {
  238. applyCodeInfoTlvValue(info, type, valueBytes)
  239. }
  240. offset = nextOffset
  241. }
  242. return info
  243. }
  244. function parseCodeInfo(bytes, options = {}) {
  245. const inputBytes = toBytes(bytes)
  246. const codeInfoByteLength = resolveCodeInfoByteLength(inputBytes.length, options)
  247. const source = inputBytes.slice(0, codeInfoByteLength)
  248. const addressWidth = normalizeCodeInfoAddressWidth(options.addressWidth)
  249. const maxPacketLength = Number(options.maxPacketLength || 0) & 0xFFFF
  250. const tlvInfo = parseCodeInfoTlvs(source)
  251. const entries = tlvInfo.entries
  252. const entryCount = entries.length
  253. const entryAddressByteLengths = entries
  254. .map((entry) => Number(entry.addressByteLength) || 0)
  255. .filter((value, index, list) => value > 0 && list.indexOf(value) === index)
  256. return {
  257. ...tlvInfo,
  258. addressWidth,
  259. addressWidthRaw: addressWidth,
  260. byteLength: codeInfoByteLength,
  261. entryCount,
  262. maxPacketLength,
  263. memoryEndian: normalizeMemoryEndian(options.memoryEndian),
  264. memoryEndianRaw: Number(options.memoryEndianRaw || options.memoryEndianMark || 0) & 0xFFFF,
  265. rawHex: bytesToHex(source, ' '),
  266. structEntryAddressByteLength: entryAddressByteLengths.length === 1 ? entryAddressByteLengths[0] : 0,
  267. structCount: entryCount,
  268. structTable: entries,
  269. tableByteLength: entries.reduce((total, entry) => total + Number(entry.rawByteLength || 0), 0),
  270. tableCapacity: entryCount,
  271. tableRemainderByteLength: 0,
  272. tlvByteLength: source.length,
  273. truncated: false
  274. }
  275. }
  276. function createRegistersForByteSpan(entry) {
  277. const count = Math.max(1, Number(entry.byteLength) || 1)
  278. const registers = []
  279. for (let offset = 0; offset < count; offset += 1) {
  280. registers.push({
  281. byteStart: offset,
  282. dataType: 'uint8_t',
  283. isPlaceholderByteField: true,
  284. name: offset.toString(16).toUpperCase().padStart(2, '0'),
  285. sourceAddress: entry.byteAddr + offset,
  286. sourceAddressByteLength: entry.addressByteLength,
  287. sourceAddressText: formatAddress(entry.byteAddr + offset, entry.addressWidth),
  288. sourceAddressWidth: entry.addressWidth,
  289. sourceByteLength: 1,
  290. sourceEntryKind: entry.entryKindText,
  291. sourceMemoryArea: entry.memoryArea,
  292. sourceMemoryClass: entry.memoryArea,
  293. sourceSymbolName: entry.typeName,
  294. sourceSymbolType: entry.typeName
  295. })
  296. }
  297. return registers
  298. }
  299. function inferVariableDataType(byteLength) {
  300. const length = Number(byteLength)
  301. if (length === 1) return 'uint8_t'
  302. if (length === 2) return 'uint16_t'
  303. if (length === 4) return 'uint32_t'
  304. return ''
  305. }
  306. function createVariableRegisters(entry) {
  307. const dataType = inferVariableDataType(entry.byteLength)
  308. if (!dataType) return createRegistersForByteSpan(entry)
  309. return [{
  310. byteStart: 0,
  311. dataType,
  312. name: entry.typeName,
  313. sourceAddress: entry.byteAddr,
  314. sourceAddressByteLength: entry.addressByteLength,
  315. sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth),
  316. sourceAddressWidth: entry.addressWidth,
  317. sourceByteLength: entry.byteLength,
  318. sourceEntryKind: entry.entryKindText,
  319. sourceMemoryArea: entry.memoryArea,
  320. sourceMemoryClass: entry.memoryArea,
  321. sourceSymbolName: entry.typeName,
  322. sourceSymbolType: entry.typeName
  323. }]
  324. }
  325. function createCodeInfoContext(codeInfo = {}) {
  326. return {
  327. addressWidth: codeInfo.addressWidth,
  328. codeInfoAddressWidth: codeInfo.addressWidth,
  329. maxPacketLength: codeInfo.maxPacketLength,
  330. memoryEndian: codeInfo.memoryEndian,
  331. storageAddressWidth: codeInfo.addressWidth
  332. }
  333. }
  334. function createSourceMeta(entry, byteLength) {
  335. return {
  336. sourceAddress: entry.byteAddr,
  337. sourceAddressByteLength: entry.addressByteLength,
  338. sourceAddressText: formatAddress(entry.byteAddr, entry.addressWidth),
  339. sourceAddressWidth: entry.addressWidth,
  340. sourceByteLength: byteLength,
  341. sourceEntryKind: entry.entryKindText,
  342. sourceMemoryArea: entry.memoryArea,
  343. sourceMemoryClass: entry.memoryArea,
  344. sourceSymbolName: entry.typeName,
  345. sourceSymbolType: entry.typeName
  346. }
  347. }
  348. function createGroupFromEntry(entry, chunkLength, suffix = '', codeInfo = {}) {
  349. const isStructEntry = entry.entryKind === CODE_INFO_ENTRY_KIND.STRUCT
  350. const registers = isStructEntry
  351. ? createRegistersForByteSpan(entry)
  352. : createVariableRegisters(entry)
  353. return {
  354. addressUnit: 'byte',
  355. codeInfoContext: createCodeInfoContext(codeInfo),
  356. layout: isStructEntry ? 'struct' : 'register',
  357. name: `${entry.typeName}${suffix}`,
  358. quantity: registers.length,
  359. registerType: entry.memoryArea === 'CODE' ? 'input' : 'holding',
  360. registers,
  361. ...createSourceMeta(entry, chunkLength),
  362. sourceSegment: 'CodeInfo TLV',
  363. sourceSegmentModule: '',
  364. startAddress: formatAddress(entry.byteAddr, entry.addressWidth)
  365. }
  366. }
  367. function createGroupsFromCodeInfo(codeInfo, options = {}) {
  368. const maxRegisters = Math.max(1, Number(options.maxRegistersPerGroup) || 256)
  369. const groups = []
  370. const entries = Array.isArray(codeInfo.entries) ? codeInfo.entries : codeInfo.structTable
  371. ;(Array.isArray(entries) ? entries : []).forEach((entry) => {
  372. if (!entry.byteLength || entry.memoryArea === 'UNKNOWN') return
  373. if (!entry.isStructEntry && !entry.isVariableEntry) return
  374. for (let offset = 0; offset < entry.byteLength; offset += maxRegisters) {
  375. const chunkLength = Math.min(maxRegisters, entry.byteLength - offset)
  376. const chunkEntry = {
  377. ...entry,
  378. byteAddr: entry.byteAddr + offset,
  379. byteLength: chunkLength
  380. }
  381. const suffix = entry.byteLength > maxRegisters ? ` #${Math.floor(offset / maxRegisters) + 1}` : ''
  382. groups.push(createGroupFromEntry(chunkEntry, chunkLength, suffix, codeInfo))
  383. }
  384. })
  385. return groups
  386. }
  387. module.exports = {
  388. CODE_INFO_ENTRY_KIND,
  389. CODE_INFO_TLV_HEADER_BYTE_LENGTH,
  390. CODE_INFO_TLV,
  391. MEMORY_ENDIAN,
  392. createGroupsFromCodeInfo,
  393. normalizeCodeInfoAddressWidth,
  394. parseCodeInfo
  395. }