index.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  1. const {
  2. padHex
  3. } = require('../../utils/base-utils.js')
  4. const {
  5. bytesToHex,
  6. bytesToWords,
  7. bytesToWordsLE,
  8. formatHexNumber,
  9. readByte,
  10. readUint16BE,
  11. readUint16LE,
  12. readUint32BE,
  13. readUint32LE,
  14. splitUint16BE,
  15. splitUint16LE,
  16. splitUint32BE,
  17. splitUint32LE,
  18. toByteArray
  19. } = require('../../utils/binary-utils.js')
  20. const {
  21. BYTE_ORDER_LOW,
  22. appendCrc16Ccitt,
  23. crc16Ccitt,
  24. hasValidCrc16Ccitt
  25. } = require('../../utils/crc.js')
  26. const PROTOCOL_NAME = 'storage-access'
  27. const CMD_ERR_MASK = 0x80
  28. const CMD_CONTROL_FLAG = 0x40
  29. const CMD_SPECIAL_OP_MASK = 0x3F
  30. const CMD_WRITE_MASK = 0x08
  31. const CMD_ADDRESS_MODE_MASK = 0x07
  32. const CMD_RESERVED_MASK = 0x30
  33. const CMD_CONTROL = CMD_CONTROL_FLAG
  34. const CONTROL_RESPONSE_HEADER_LENGTH = 1
  35. const ADDRESS16_BYTE_LENGTH = 2
  36. const ADDRESS32_BYTE_LENGTH = 4
  37. const AREA = {
  38. CODEINFO: 0x00,
  39. ADDR32: 0x07,
  40. DATA: 0x01,
  41. IDATA: 0x02,
  42. XDATA: 0x03,
  43. CODE: 0x04
  44. }
  45. const CONTROL_OP = {
  46. RESET: 0x01
  47. }
  48. const AREA_NAMES = {
  49. [AREA.CODEINFO]: 'CODEINFO',
  50. [AREA.ADDR32]: 'ADDR32',
  51. [AREA.DATA]: 'DATA',
  52. [AREA.IDATA]: 'IDATA',
  53. [AREA.XDATA]: 'XDATA',
  54. [AREA.CODE]: 'CODE'
  55. }
  56. const AREA_BY_NAME = {
  57. CODEINFO: AREA.CODEINFO,
  58. CODE_INFO: AREA.CODEINFO,
  59. ADDR32: AREA.ADDR32,
  60. ADDRESS32: AREA.ADDR32,
  61. DATA: AREA.DATA,
  62. IDATA: AREA.IDATA,
  63. XDATA: AREA.XDATA,
  64. CODE: AREA.CODE
  65. }
  66. const EXCEPTION_MESSAGES = {
  67. 0x01: '非法命令',
  68. 0x02: '非法区域',
  69. 0x03: '非法地址',
  70. 0x04: '非法长度',
  71. 0x05: '写保护',
  72. 0x06: '设备忙',
  73. 0x07: '格式错误',
  74. 0x08: '访问被拒绝',
  75. 0x09: '内部错误',
  76. 0x0A: '对齐错误',
  77. 0x0B: '范围溢出',
  78. 0x0C: '不支持的操作'
  79. }
  80. const DEFAULT_MAX_FRAME_BYTES = 64
  81. const UNLIMITED_FRAME_BYTES = 0
  82. const MAX_UINT16 = 0xFFFF
  83. const MAX_UINT32 = 0xFFFFFFFF
  84. const MAX_PAYLOAD_BYTES = MAX_UINT16
  85. const READ_REQUEST_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
  86. const READ_REQUEST_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
  87. const WRITE_REQUEST_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
  88. const WRITE_REQUEST_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
  89. const READ_RESPONSE_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
  90. const READ_RESPONSE_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
  91. const WRITE_RESPONSE_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
  92. const WRITE_RESPONSE_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
  93. const EXCEPTION_RESPONSE_LENGTH = 4
  94. const CODE_INFO_DESCRIPTOR_ADDRESS = 0
  95. const CODE_INFO_DESCRIPTOR_BYTE_LENGTH = 11
  96. const CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH = 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH + 2
  97. const MEMORY_ENDIAN_MARK_BIG = 0x55AA
  98. const MEMORY_ENDIAN_MARK_LITTLE = 0xAA55
  99. const MEMORY_ENDIAN = {
  100. BIG: 'big',
  101. LITTLE: 'little'
  102. }
  103. const STORAGE_CRC_OPTIONS = {
  104. byteOrder: BYTE_ORDER_LOW
  105. }
  106. const VALID_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
  107. const MEMORY_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
  108. const WRITABLE_AREAS = [AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA]
  109. const RESERVED_AREAS = [0x05, 0x06]
  110. const readCrcWord = readUint16LE
  111. function normalizeMemoryEndian(value, fallback = MEMORY_ENDIAN.LITTLE) {
  112. const text = String(value || '').trim().toLowerCase()
  113. if (text === MEMORY_ENDIAN.LITTLE || text === 'le' || text === '1') return MEMORY_ENDIAN.LITTLE
  114. if (text === MEMORY_ENDIAN.BIG || text === 'be' || text === '0') return MEMORY_ENDIAN.BIG
  115. return fallback
  116. }
  117. function isLittleMemoryEndian(memoryEndian) {
  118. return normalizeMemoryEndian(memoryEndian) === MEMORY_ENDIAN.LITTLE
  119. }
  120. function readWord(bytes, offset, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  121. return isLittleMemoryEndian(memoryEndian)
  122. ? readUint16LE(bytes, offset)
  123. : readUint16BE(bytes, offset)
  124. }
  125. function readDword(bytes, offset, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  126. return isLittleMemoryEndian(memoryEndian)
  127. ? readUint32LE(bytes, offset)
  128. : readUint32BE(bytes, offset)
  129. }
  130. function splitWord(value, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  131. return isLittleMemoryEndian(memoryEndian)
  132. ? splitUint16LE(value)
  133. : splitUint16BE(value)
  134. }
  135. function splitDword(value, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  136. return isLittleMemoryEndian(memoryEndian)
  137. ? splitUint32LE(value)
  138. : splitUint32BE(value)
  139. }
  140. function bytesToProtocolWords(bytes, memoryEndian = MEMORY_ENDIAN.LITTLE) {
  141. return isLittleMemoryEndian(memoryEndian) ? bytesToWordsLE(bytes) : bytesToWords(bytes)
  142. }
  143. function toByte(value, label) {
  144. if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
  145. throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
  146. }
  147. return value
  148. }
  149. function toWord(value, label) {
  150. if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
  151. throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
  152. }
  153. return value
  154. }
  155. function toUint32(value, label) {
  156. if (!Number.isInteger(value) || value < 0 || value > MAX_UINT32) {
  157. throw new Error(`${label}必须在 0x00000000 至 0xFFFFFFFF 之间`)
  158. }
  159. return value
  160. }
  161. function toAccessLength(value, label = '访问字节长度') {
  162. const numberValue = Number(value)
  163. if (!Number.isFinite(numberValue)) {
  164. throw new Error(`${label}必须大于 0`)
  165. }
  166. const byteLength = Math.floor(numberValue)
  167. if (byteLength <= 0) {
  168. throw new Error(`${label}必须大于 0`)
  169. }
  170. return byteLength
  171. }
  172. function normalizeArea(value) {
  173. if (typeof value === 'string') {
  174. const area = AREA_BY_NAME[value.trim().toUpperCase()]
  175. if (area !== undefined) return area
  176. }
  177. const area = toByte(Number(value), '存储区域')
  178. if (VALID_AREAS.indexOf(area) < 0) {
  179. throw new Error('存储区域必须为 codeinfo/data/idata/xdata/code/addr32')
  180. }
  181. return area
  182. }
  183. function isAddress32Area(area) {
  184. return Number(area) === AREA.ADDR32
  185. }
  186. function getAddressFieldByteLength(area) {
  187. return isAddress32Area(area) ? ADDRESS32_BYTE_LENGTH : ADDRESS16_BYTE_LENGTH
  188. }
  189. function getAddressMaxValue(area) {
  190. return getAddressFieldByteLength(area) === ADDRESS32_BYTE_LENGTH ? MAX_UINT32 : MAX_UINT16
  191. }
  192. function validateMemoryAccessRange(area, address, byteLength, label = '访问') {
  193. const normalizedArea = normalizeMemoryArea(area)
  194. const addressBytes = getAddressFieldByteLength(normalizedArea)
  195. const startAddress = addressBytes === ADDRESS32_BYTE_LENGTH
  196. ? toUint32(Number(address), '内存地址')
  197. : toWord(Number(address), '内存地址')
  198. const length = toAccessLength(byteLength, `${label}字节长度`)
  199. const maxAddress = getAddressMaxValue(normalizedArea)
  200. const endAddress = startAddress + length - 1
  201. if (endAddress > maxAddress) {
  202. throw new Error(`${label}范围超过 0x${formatHexNumber(maxAddress, addressBytes * 2)} 地址上限`)
  203. }
  204. return {
  205. length,
  206. startAddress
  207. }
  208. }
  209. function getMemoryHeaderLength(area) {
  210. return 1 + getAddressFieldByteLength(area) + 2
  211. }
  212. function getReadRequestLength(area) {
  213. if (Number(area) === AREA.CODEINFO) return 3
  214. return getMemoryHeaderLength(area) + 2
  215. }
  216. function getWriteRequestOverhead(area) {
  217. return getMemoryHeaderLength(area) + 2
  218. }
  219. function getReadResponseOverhead(area) {
  220. if (Number(area) === AREA.CODEINFO) return 1 + 2
  221. return getMemoryHeaderLength(area) + 2
  222. }
  223. function getWriteResponseLength(area) {
  224. return getMemoryHeaderLength(area) + 2
  225. }
  226. function isCodeInfoDescriptorArea(area) {
  227. return Number(area) === AREA.CODEINFO
  228. }
  229. function normalizeMemoryArea(value) {
  230. const area = normalizeArea(value)
  231. if (MEMORY_AREAS.indexOf(area) < 0) {
  232. throw new Error('存储访问区域必须为 codeinfo/data/idata/xdata/code/addr32')
  233. }
  234. return area
  235. }
  236. function toByteLength(value, label = '字节长度', maxPayload = MAX_PAYLOAD_BYTES) {
  237. const byteLength = toWord(Number(value), label)
  238. if (byteLength === 0) {
  239. throw new Error(`${label}必须大于 0`)
  240. }
  241. if (maxPayload > 0 && byteLength > maxPayload) {
  242. throw new Error(`单帧最多访问 ${maxPayload} 字节`)
  243. }
  244. return byteLength
  245. }
  246. function normalizeDescriptorAddressWidth(value) {
  247. const numberValue = Number(value)
  248. if (numberValue === 16 || numberValue === 32) return numberValue
  249. throw new Error('CodeInfo 描述符地址长度必须为 16 或 32')
  250. }
  251. function parseMemoryEndianMark(bytes, offset) {
  252. const firstByte = readByte(bytes, offset)
  253. const secondByte = readByte(bytes, offset + 1)
  254. const marker = ((firstByte << 8) | secondByte) & 0xFFFF
  255. if (firstByte === 0x55 && secondByte === 0xAA) {
  256. return {
  257. marker: MEMORY_ENDIAN_MARK_BIG,
  258. memoryEndian: MEMORY_ENDIAN.BIG
  259. }
  260. }
  261. if (firstByte === 0xAA && secondByte === 0x55) {
  262. return {
  263. marker: MEMORY_ENDIAN_MARK_LITTLE,
  264. memoryEndian: MEMORY_ENDIAN.LITTLE
  265. }
  266. }
  267. return {
  268. marker,
  269. memoryEndian: ''
  270. }
  271. }
  272. function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
  273. const numberValue = Number(maxFrameBytes)
  274. if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
  275. if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
  276. return DEFAULT_MAX_FRAME_BYTES
  277. }
  278. function resolveDescriptorMaxFrameBytes(configuredMaxFrameBytes, descriptorMaxFrameBytes) {
  279. const configured = normalizeMaxFrameBytes(configuredMaxFrameBytes)
  280. const descriptor = normalizeMaxFrameBytes(descriptorMaxFrameBytes)
  281. if (descriptor === UNLIMITED_FRAME_BYTES) return configured
  282. if (configured === UNLIMITED_FRAME_BYTES) return descriptor
  283. return Math.min(configured, descriptor)
  284. }
  285. function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
  286. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  287. if (frameBytes === UNLIMITED_FRAME_BYTES) return MAX_PAYLOAD_BYTES
  288. return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
  289. }
  290. function getRequiredPayloadLimit(maxFrameBytes, overhead, actionText) {
  291. const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
  292. const payloadLimit = getPayloadLimitFromFrame(frameBytes, overhead)
  293. if (frameBytes !== UNLIMITED_FRAME_BYTES && payloadLimit <= 0) {
  294. throw new Error(`最大包长至少需要 ${overhead + 1} 字节才能${actionText}`)
  295. }
  296. return payloadLimit
  297. }
  298. function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
  299. return getPayloadLimitFromFrame(maxFrameBytes, getReadResponseOverhead(area))
  300. }
  301. function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
  302. return getPayloadLimitFromFrame(maxFrameBytes, getWriteRequestOverhead(area))
  303. }
  304. function buildCommand(area, isWrite = false) {
  305. const normalizedArea = normalizeMemoryArea(area)
  306. if (RESERVED_AREAS.indexOf(normalizedArea) >= 0) {
  307. throw new Error('存储访问区域号 0x05/0x06 暂时保留')
  308. }
  309. if (isWrite && WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
  310. throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
  311. }
  312. return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
  313. }
  314. function buildSpecialCommand(operation) {
  315. const op = toByte(Number(operation), '特殊指令') & CMD_SPECIAL_OP_MASK
  316. if (op <= 0) {
  317. throw new Error('特殊指令必须在 0x01 至 0x3F 之间')
  318. }
  319. return CMD_CONTROL_FLAG | op
  320. }
  321. function decodeCommand(command) {
  322. const cmd = toByte(Number(command), '命令字')
  323. const sourceCommand = cmd & ~CMD_ERR_MASK
  324. const isControl = !!(sourceCommand & CMD_CONTROL_FLAG)
  325. const operation = isControl ? (sourceCommand & CMD_SPECIAL_OP_MASK) : 0
  326. const area = isControl ? CMD_CONTROL : (sourceCommand & CMD_ADDRESS_MODE_MASK)
  327. const reservedBits = sourceCommand & CMD_RESERVED_MASK
  328. return {
  329. addressBytes: isControl ? 0 : getAddressFieldByteLength(area),
  330. area,
  331. command: cmd,
  332. hasError: !!(cmd & CMD_ERR_MASK),
  333. hasInvalidSpecialOperation: isControl && operation === 0,
  334. hasReservedBits: !isControl && reservedBits !== 0,
  335. isAddress32: !isControl && isAddress32Area(area),
  336. isControl,
  337. isWrite: !isControl && !!(sourceCommand & CMD_WRITE_MASK),
  338. operation,
  339. sourceCommand
  340. }
  341. }
  342. function hasValidStorageCrc(bytes) {
  343. const frame = toByteArray(bytes)
  344. if (frame.length < 3) return false
  345. if (frame.length >= 4) return hasValidCrc16Ccitt(frame, STORAGE_CRC_OPTIONS)
  346. const expected = crc16Ccitt(frame.slice(0, -2), STORAGE_CRC_OPTIONS)
  347. const received = readCrcWord(frame, frame.length - 2)
  348. return expected === received
  349. }
  350. function appendStorageCrc(bytes) {
  351. return appendCrc16Ccitt(bytes, STORAGE_CRC_OPTIONS)
  352. }
  353. function buildReadFrame(area, address, byteLength, options = {}) {
  354. const normalizedArea = normalizeMemoryArea(area)
  355. if (isCodeInfoDescriptorArea(normalizedArea)) {
  356. return buildCodeInfoDescriptorFrame()
  357. }
  358. const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  359. const maxByteLength = getRequiredPayloadLimit(
  360. options.maxFrameBytes,
  361. getReadResponseOverhead(normalizedArea),
  362. '读取 1 字节'
  363. )
  364. const length = toByteLength(Number(byteLength), '读取字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  365. const addressBytes = getAddressFieldByteLength(normalizedArea)
  366. const range = validateMemoryAccessRange(normalizedArea, address, length, '读取')
  367. const startAddress = range.startAddress
  368. const command = buildCommand(normalizedArea, false)
  369. const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH
  370. ? splitDword(startAddress, memoryEndian)
  371. : splitWord(startAddress, memoryEndian)
  372. const lengthParts = splitWord(toWord(length, '读取字节长度'), memoryEndian)
  373. return appendStorageCrc([command].concat(addressParts, lengthParts))
  374. }
  375. function buildWriteFrame(area, address, bytes, options = {}) {
  376. const normalizedArea = normalizeMemoryArea(area)
  377. if (WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
  378. throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
  379. }
  380. const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  381. const dataBytes = toByteArray(bytes).map((byte) => toByte(Number(byte), '写入字节'))
  382. const maxByteLength = getRequiredPayloadLimit(
  383. options.maxFrameBytes,
  384. getWriteRequestOverhead(normalizedArea),
  385. '写入 1 字节'
  386. )
  387. const length = toByteLength(dataBytes.length, '写入字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
  388. const addressBytes = getAddressFieldByteLength(normalizedArea)
  389. const range = validateMemoryAccessRange(normalizedArea, address, length, '写入')
  390. const startAddress = range.startAddress
  391. const command = buildCommand(normalizedArea, true)
  392. const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH
  393. ? splitDword(startAddress, memoryEndian)
  394. : splitWord(startAddress, memoryEndian)
  395. const lengthParts = splitWord(toWord(length, '写入字节长度'), memoryEndian)
  396. return appendStorageCrc([command].concat(addressParts, lengthParts, dataBytes))
  397. }
  398. function buildControlFrame(operation, dataBytes = []) {
  399. const command = buildSpecialCommand(operation)
  400. const payload = toByteArray(dataBytes).map((byte) => toByte(Number(byte), '指令数据'))
  401. return appendStorageCrc([command].concat(payload))
  402. }
  403. function buildCodeInfoDescriptorFrame() {
  404. return appendStorageCrc([buildCommand(AREA.CODEINFO, false)])
  405. }
  406. function parseCodeInfoDescriptorBytes(bytes) {
  407. const dataBytes = toByteArray(bytes)
  408. if (dataBytes.length < CODE_INFO_DESCRIPTOR_BYTE_LENGTH) {
  409. throw new Error('CodeInfo 描述符长度无效')
  410. }
  411. const endianMark = parseMemoryEndianMark(dataBytes, 9)
  412. if (!endianMark.memoryEndian) {
  413. throw new Error('CodeInfo 描述符字节序标记无效')
  414. }
  415. const memoryEndian = endianMark.memoryEndian
  416. return {
  417. codeInfoAddress: readDword(dataBytes, 0, memoryEndian),
  418. codeInfoAddressWidth: dataBytes[6] & 0xFF,
  419. codeInfoByteLength: readWord(dataBytes, 4, memoryEndian),
  420. codeInfoDescriptorBytes: dataBytes.slice(0, CODE_INFO_DESCRIPTOR_BYTE_LENGTH),
  421. codeInfoMaxPacketLength: readWord(dataBytes, 7, memoryEndian),
  422. codeInfoMemoryEndian: endianMark.memoryEndian,
  423. codeInfoMemoryEndianMark: endianMark.marker
  424. }
  425. }
  426. function formatHex(bytes) {
  427. return bytesToHex(bytes, ' ')
  428. }
  429. function parseStorageAccessResponse(bytes, options = {}) {
  430. const frame = toByteArray(bytes)
  431. if (frame.length < 3 || !hasValidStorageCrc(frame)) return null
  432. const command = frame[0] & 0xFF
  433. const decoded = decodeCommand(command)
  434. if (decoded.isControl) return parseStorageControlResponse(frame, decoded)
  435. const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  436. if (decoded.hasError) {
  437. if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
  438. return {
  439. area: decoded.area,
  440. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  441. command,
  442. exceptionCode: frame[1] & 0xFF,
  443. isException: true,
  444. isWrite: decoded.isWrite,
  445. protocol: PROTOCOL_NAME,
  446. sourceCommand: decoded.sourceCommand
  447. }
  448. }
  449. if (decoded.hasReservedBits) return null
  450. if (!AREA_NAMES[decoded.area]) return null
  451. if (isCodeInfoDescriptorArea(decoded.area)) {
  452. if (decoded.isWrite || frame.length !== CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH) return null
  453. const dataBytes = frame.slice(1, 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH)
  454. const descriptor = parseCodeInfoDescriptorBytes(dataBytes)
  455. const response = {
  456. address: CODE_INFO_DESCRIPTOR_ADDRESS,
  457. addressWidth: 0,
  458. area: decoded.area,
  459. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  460. byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
  461. command,
  462. dataBytes,
  463. isException: false,
  464. isAddress32: false,
  465. isWrite: false,
  466. protocol: PROTOCOL_NAME,
  467. words: bytesToProtocolWords(
  468. dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0),
  469. descriptor.codeInfoMemoryEndian
  470. )
  471. }
  472. return {
  473. ...response,
  474. ...descriptor
  475. }
  476. }
  477. const addressBytes = decoded.addressBytes
  478. const headerLength = getMemoryHeaderLength(decoded.area)
  479. if (decoded.isWrite) {
  480. if (frame.length !== getWriteResponseLength(decoded.area)) return null
  481. return {
  482. address: decoded.isAddress32 ? readDword(frame, 1, memoryEndian) : readWord(frame, 1, memoryEndian),
  483. addressWidth: decoded.isAddress32 ? 32 : 16,
  484. area: decoded.area,
  485. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  486. byteLength: readWord(frame, 1 + addressBytes, memoryEndian),
  487. command,
  488. dataBytes: [],
  489. isException: false,
  490. isAddress32: decoded.isAddress32,
  491. isWrite: true,
  492. protocol: PROTOCOL_NAME
  493. }
  494. }
  495. if (frame.length < getReadResponseOverhead(decoded.area)) return null
  496. const address = decoded.isAddress32 ? readDword(frame, 1, memoryEndian) : readWord(frame, 1, memoryEndian)
  497. const byteLength = readWord(frame, 1 + addressBytes, memoryEndian)
  498. const dataStart = headerLength
  499. const dataEnd = dataStart + byteLength
  500. if (frame.length !== dataEnd + 2) return null
  501. const dataBytes = frame.slice(dataStart, dataEnd)
  502. const response = {
  503. address,
  504. addressWidth: decoded.isAddress32 ? 32 : 16,
  505. area: decoded.area,
  506. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  507. byteLength,
  508. command,
  509. dataBytes,
  510. isException: false,
  511. isAddress32: decoded.isAddress32,
  512. isWrite: false,
  513. protocol: PROTOCOL_NAME,
  514. words: bytesToProtocolWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0), memoryEndian)
  515. }
  516. return response
  517. }
  518. function parseStorageControlResponse(frame, decoded) {
  519. if (decoded.hasInvalidSpecialOperation) return null
  520. if (decoded.hasError) {
  521. if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
  522. return {
  523. area: CMD_CONTROL,
  524. areaName: 'CONTROL',
  525. command: frame[0] & 0xFF,
  526. exceptionCode: frame[1] & 0xFF,
  527. isControl: true,
  528. isException: true,
  529. isWrite: false,
  530. operation: decoded.operation,
  531. protocol: PROTOCOL_NAME,
  532. sourceCommand: decoded.sourceCommand
  533. }
  534. }
  535. if (frame.length < CONTROL_RESPONSE_HEADER_LENGTH + 2) return null
  536. const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
  537. const dataEnd = frame.length - 2
  538. const byteLength = Math.max(0, dataEnd - dataStart)
  539. const dataBytes = frame.slice(dataStart, dataEnd)
  540. return {
  541. area: CMD_CONTROL,
  542. areaName: 'CONTROL',
  543. byteLength,
  544. command: frame[0] & 0xFF,
  545. dataBytes,
  546. isControl: true,
  547. isException: false,
  548. isWrite: false,
  549. operation: decoded.operation,
  550. protocol: PROTOCOL_NAME
  551. }
  552. }
  553. function parseStorageAccessRequest(bytes, options = {}) {
  554. const frame = toByteArray(bytes)
  555. if (frame.length < 3 || !hasValidStorageCrc(frame)) return null
  556. const command = frame[0] & 0xFF
  557. const decoded = decodeCommand(command)
  558. if (decoded.isControl) return parseStorageControlRequest(frame, decoded)
  559. if (decoded.hasError) return null
  560. if (decoded.hasReservedBits) return null
  561. if (!AREA_NAMES[decoded.area]) return null
  562. if (isCodeInfoDescriptorArea(decoded.area)) {
  563. if (decoded.isWrite || frame.length !== getReadRequestLength(decoded.area)) return null
  564. return {
  565. address: CODE_INFO_DESCRIPTOR_ADDRESS,
  566. addressWidth: 0,
  567. area: decoded.area,
  568. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  569. byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
  570. command,
  571. dataBytes: [],
  572. isAddress32: false,
  573. isWrite: false,
  574. kind: 'raw-hex',
  575. operation: 'read',
  576. protocol: PROTOCOL_NAME,
  577. quantity: CODE_INFO_DESCRIPTOR_BYTE_LENGTH
  578. }
  579. }
  580. if (frame.length < READ_REQUEST_LENGTH_16) return null
  581. const memoryEndian = normalizeMemoryEndian(options.memoryEndian)
  582. const addressBytes = decoded.addressBytes
  583. const headerLength = getMemoryHeaderLength(decoded.area)
  584. if (frame.length < headerLength + 2) return null
  585. const address = decoded.isAddress32 ? readDword(frame, 1, memoryEndian) : readWord(frame, 1, memoryEndian)
  586. const byteLength = readWord(frame, 1 + addressBytes, memoryEndian)
  587. const expectedLength = decoded.isWrite
  588. ? headerLength + byteLength + 2
  589. : headerLength + 2
  590. if (byteLength <= 0 || frame.length !== expectedLength) return null
  591. return {
  592. address,
  593. addressWidth: decoded.isAddress32 ? 32 : 16,
  594. area: decoded.area,
  595. areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
  596. byteLength,
  597. command,
  598. dataBytes: decoded.isWrite ? frame.slice(headerLength, headerLength + byteLength) : [],
  599. isAddress32: decoded.isAddress32,
  600. isWrite: decoded.isWrite,
  601. kind: 'raw-hex',
  602. operation: decoded.isWrite ? 'write' : 'read',
  603. protocol: PROTOCOL_NAME,
  604. quantity: byteLength
  605. }
  606. }
  607. function parseStorageControlRequest(frame, decoded = decodeCommand(frame && frame[0])) {
  608. if (frame.length < 3 || decoded.hasInvalidSpecialOperation) return null
  609. const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
  610. const dataEnd = frame.length - 2
  611. const byteLength = Math.max(0, dataEnd - dataStart)
  612. return {
  613. area: CMD_CONTROL,
  614. areaName: 'CONTROL',
  615. byteLength,
  616. command: frame[0] & 0xFF,
  617. dataBytes: frame.slice(dataStart, dataEnd),
  618. isControl: true,
  619. isWrite: false,
  620. kind: 'storage-control',
  621. operation: decoded.operation,
  622. protocol: PROTOCOL_NAME
  623. }
  624. }
  625. function getExpectedResponseLength(expected, responseCommand, responseBytes = []) {
  626. if (!expected) return 0
  627. const command = Number(responseCommand) & 0xFF
  628. if (command === (expected.command | CMD_ERR_MASK)) return EXCEPTION_RESPONSE_LENGTH
  629. if (command !== expected.command) return 0
  630. if (expected.isControl) {
  631. if (responseBytes.length < CONTROL_RESPONSE_HEADER_LENGTH) return 0
  632. return CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
  633. }
  634. if (expected.operation === 'write' || expected.isWrite) {
  635. return getWriteResponseLength(expected.area)
  636. }
  637. if (isCodeInfoDescriptorArea(expected.area)) {
  638. return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
  639. }
  640. const headerLength = getMemoryHeaderLength(expected.area)
  641. if (responseBytes.length < headerLength) return 0
  642. return headerLength + readWord(
  643. responseBytes,
  644. 1 + getAddressFieldByteLength(expected.area),
  645. expected.memoryEndian
  646. ) + 2
  647. }
  648. function isExpectedResponse(response, expected) {
  649. if (!response || !expected) return false
  650. const sourceCommand = response.isException ? response.sourceCommand : response.command
  651. if (sourceCommand !== expected.command) return false
  652. if (expected.isControl) {
  653. if (!response.isControl) return false
  654. if (response.isException) return true
  655. if (response.operation !== expected.operation) return false
  656. return true
  657. }
  658. if (!response.isException && response.area !== expected.area) return false
  659. if (response.isException) return true
  660. if (response.address !== expected.address) return false
  661. if (response.byteLength !== expected.byteLength) return false
  662. if (!response.isWrite && (!Array.isArray(response.dataBytes) || response.dataBytes.length !== expected.byteLength)) return false
  663. return true
  664. }
  665. function getExceptionText(code) {
  666. return EXCEPTION_MESSAGES[code] || '未知异常'
  667. }
  668. function formatExceptionMessage(response) {
  669. const sourceCommand = response && response.sourceCommand
  670. const exceptionCode = response && response.exceptionCode
  671. const exceptionText = getExceptionText(exceptionCode)
  672. return `设备返回异常帧:命令 0x${padHex(sourceCommand, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
  673. }
  674. function getReadBufferHint(expected) {
  675. if (!expected) return 0
  676. if (expected.isControl) return expected.responseBufferHint || CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
  677. if (expected.operation === 'write' || expected.isWrite) {
  678. return getWriteResponseLength(expected.area)
  679. }
  680. if (isCodeInfoDescriptorArea(expected.area)) {
  681. return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
  682. }
  683. return getReadResponseOverhead(expected.area) + Number(expected.byteLength || expected.quantity || 0)
  684. }
  685. function alignResponseBuffer(buffer, expected) {
  686. if (!Array.isArray(buffer) || !buffer.length || !expected) return
  687. const expectedCommands = [expected.command, expected.command | CMD_ERR_MASK]
  688. let matchIndex = -1
  689. for (let index = 0; index < buffer.length; index += 1) {
  690. if (expectedCommands.indexOf(buffer[index]) < 0) continue
  691. matchIndex = index
  692. break
  693. }
  694. if (matchIndex > 0) {
  695. buffer.splice(0, matchIndex)
  696. } else if (matchIndex < 0 && buffer.length > 1) {
  697. buffer.splice(0, buffer.length - 1)
  698. }
  699. }
  700. function readResponseFromBuffer(buffer, expected, options = {}) {
  701. if (!Array.isArray(buffer) || !buffer.length || !expected) {
  702. return {
  703. status: 'pending'
  704. }
  705. }
  706. alignResponseBuffer(buffer, expected)
  707. while (buffer.length >= 1) {
  708. const responseCommand = buffer[0]
  709. const responseLength = getExpectedResponseLength(expected, responseCommand, buffer)
  710. if (!responseLength) {
  711. return {
  712. status: 'pending'
  713. }
  714. }
  715. const frameLimit = normalizeMaxFrameBytes(
  716. options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
  717. )
  718. if (frameLimit > 0 && responseLength > frameLimit) {
  719. return {
  720. frameLimit,
  721. responseLength,
  722. status: 'frame-too-long'
  723. }
  724. }
  725. if (buffer.length < responseLength) {
  726. return {
  727. status: 'pending'
  728. }
  729. }
  730. const frameBytes = buffer.slice(0, responseLength)
  731. let response
  732. try {
  733. response = parseStorageAccessResponse(frameBytes, expected)
  734. } catch (error) {
  735. return {
  736. frameBytes,
  737. message: error && error.message ? error.message : '存储访问响应帧解析失败',
  738. status: 'invalid'
  739. }
  740. }
  741. if (!response) {
  742. return {
  743. frameBytes,
  744. status: 'invalid'
  745. }
  746. }
  747. if (!isExpectedResponse(response, expected)) {
  748. buffer.shift()
  749. alignResponseBuffer(buffer, expected)
  750. continue
  751. }
  752. if (response.isException) {
  753. return {
  754. message: formatExceptionMessage(response),
  755. response,
  756. status: 'exception'
  757. }
  758. }
  759. buffer.splice(0, responseLength)
  760. return {
  761. response,
  762. status: 'complete'
  763. }
  764. }
  765. return {
  766. status: 'pending'
  767. }
  768. }
  769. function createExpected(area, address, byteLength, isWrite, kind, options = {}) {
  770. const normalizedArea = normalizeMemoryArea(area)
  771. const command = buildCommand(normalizedArea, isWrite)
  772. return {
  773. address,
  774. addressWidth: isAddress32Area(normalizedArea) ? 32 : 16,
  775. area: normalizedArea,
  776. byteLength,
  777. command,
  778. isAddress32: isAddress32Area(normalizedArea),
  779. isWrite,
  780. kind,
  781. memoryEndian: normalizeMemoryEndian(options.memoryEndian),
  782. operation: isWrite ? 'write' : 'read',
  783. protocol: PROTOCOL_NAME,
  784. quantity: byteLength
  785. }
  786. }
  787. function createControlExpected(operation, kind = 'storage-control', options = {}) {
  788. const op = toByte(Number(operation), '特殊指令')
  789. const command = buildSpecialCommand(op)
  790. return {
  791. area: CMD_CONTROL,
  792. byteLength: 0,
  793. command,
  794. expectedByteLength: Number(options.expectedByteLength) || 0,
  795. isControl: true,
  796. isWrite: false,
  797. kind,
  798. operation: op,
  799. protocol: PROTOCOL_NAME,
  800. responseBufferHint: CONTROL_RESPONSE_HEADER_LENGTH + (Number(options.expectedByteLength) || 0) + 2
  801. }
  802. }
  803. function formatAddress(value) {
  804. return formatHexNumber(value, 1).replace(/^0+(?=[0-9A-F])/, '')
  805. }
  806. function getChunkLabel(label, chunks, chunk) {
  807. if (!label || chunks.length <= 1) return label
  808. return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
  809. }
  810. function splitQuantity(startAddress, quantity, maxQuantity) {
  811. const chunks = []
  812. let address = Number(startAddress) || 0
  813. let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  814. const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
  815. while (remaining > 0) {
  816. const chunkQuantity = Math.min(remaining, chunkLimit)
  817. chunks.push({
  818. address,
  819. quantity: chunkQuantity
  820. })
  821. address += chunkQuantity
  822. remaining -= chunkQuantity
  823. }
  824. return chunks
  825. }
  826. function getReadChunks(startAddress, byteLength, options = {}) {
  827. const normalizedArea = normalizeMemoryArea(options.area === undefined ? AREA.ADDR32 : options.area)
  828. if (isCodeInfoDescriptorArea(normalizedArea)) {
  829. const frameLimit = normalizeMaxFrameBytes(options.maxFrameBytes)
  830. if (frameLimit !== UNLIMITED_FRAME_BYTES && frameLimit < CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH) {
  831. throw new Error(`最大包长至少需要 ${CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH} 字节才能读取 CodeInfo 描述符`)
  832. }
  833. return [{
  834. address: CODE_INFO_DESCRIPTOR_ADDRESS,
  835. quantity: CODE_INFO_DESCRIPTOR_BYTE_LENGTH
  836. }]
  837. }
  838. const range = validateMemoryAccessRange(normalizedArea, startAddress, byteLength, '读取')
  839. const maxByteLength = getRequiredPayloadLimit(
  840. options.maxFrameBytes,
  841. getReadResponseOverhead(normalizedArea),
  842. '读取 1 字节'
  843. )
  844. return splitQuantity(range.startAddress, range.length, maxByteLength)
  845. }
  846. function getWriteChunks(startAddress, bytes, options = {}) {
  847. const sourceBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
  848. const normalizedArea = normalizeMemoryArea(options.area === undefined ? AREA.ADDR32 : options.area)
  849. const range = validateMemoryAccessRange(normalizedArea, startAddress, sourceBytes.length, '写入')
  850. const maxByteLength = getRequiredPayloadLimit(
  851. options.maxFrameBytes,
  852. getWriteRequestOverhead(normalizedArea),
  853. '写入 1 字节'
  854. )
  855. const chunks = splitQuantity(range.startAddress, range.length, maxByteLength)
  856. let offset = 0
  857. return chunks.map((chunk) => {
  858. const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
  859. offset += chunk.quantity
  860. return {
  861. ...chunk,
  862. dataBytes
  863. }
  864. })
  865. }
  866. const response = {
  867. createControlExpected,
  868. createExpected,
  869. formatExceptionMessage,
  870. getExceptionText,
  871. getExpectedResponseLength,
  872. getReadBufferHint,
  873. isExpectedResponse,
  874. parseStorageAccessRequest,
  875. parseStorageAccessResponse,
  876. readResponseFromBuffer
  877. }
  878. module.exports = {
  879. AREA,
  880. AREA_BY_NAME,
  881. AREA_NAMES,
  882. ADDRESS16_BYTE_LENGTH,
  883. ADDRESS32_BYTE_LENGTH,
  884. CMD_CONTROL,
  885. CMD_CONTROL_FLAG,
  886. CMD_SPECIAL_OP_MASK,
  887. CMD_ADDRESS_MODE_MASK,
  888. CMD_ERR_MASK,
  889. CMD_RESERVED_MASK,
  890. CMD_WRITE_MASK,
  891. CONTROL_OP,
  892. CONTROL_RESPONSE_HEADER_LENGTH,
  893. DEFAULT_MAX_FRAME_BYTES,
  894. EXCEPTION_MESSAGES,
  895. EXCEPTION_RESPONSE_LENGTH,
  896. CODE_INFO_DESCRIPTOR_ADDRESS,
  897. CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
  898. MAX_PAYLOAD_BYTES,
  899. MEMORY_ENDIAN,
  900. MEMORY_ENDIAN_MARK_BIG,
  901. MEMORY_ENDIAN_MARK_LITTLE,
  902. PROTOCOL_NAME,
  903. READ_REQUEST_LENGTH_16,
  904. READ_REQUEST_LENGTH_32,
  905. READ_RESPONSE_OVERHEAD_16,
  906. READ_RESPONSE_OVERHEAD_32,
  907. STORAGE_CRC_OPTIONS,
  908. UNLIMITED_FRAME_BYTES,
  909. WRITE_REQUEST_OVERHEAD_16,
  910. WRITE_REQUEST_OVERHEAD_32,
  911. WRITE_RESPONSE_LENGTH_16,
  912. WRITE_RESPONSE_LENGTH_32,
  913. appendStorageCrc,
  914. buildCommand,
  915. buildCodeInfoDescriptorFrame,
  916. buildControlFrame,
  917. buildReadFrame,
  918. buildSpecialCommand,
  919. buildWriteFrame,
  920. createControlExpected,
  921. createExpected,
  922. decodeCommand,
  923. formatHex,
  924. getChunkLabel,
  925. getReadChunks,
  926. getWriteChunks,
  927. getMaxReadByteLength,
  928. getMaxWriteByteLength,
  929. hasValidStorageCrc,
  930. normalizeDescriptorAddressWidth,
  931. normalizeArea,
  932. normalizeMaxFrameBytes,
  933. normalizeMemoryEndian,
  934. normalizeMemoryArea,
  935. parseCodeInfoDescriptorBytes,
  936. resolveDescriptorMaxFrameBytes,
  937. response,
  938. splitDword,
  939. splitQuantity,
  940. splitWord,
  941. toByte,
  942. toByteLength,
  943. toUint32,
  944. toWord
  945. }