| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965 |
- const {
- padHex
- } = require('../../utils/base-utils.js')
- const {
- bytesToHex,
- bytesToWordsLE,
- formatHexNumber,
- readByte,
- readUint16LE,
- readUint32LE,
- splitUint16LE,
- splitUint32LE,
- toByteArray
- } = require('../../utils/binary-utils.js')
- const {
- BYTE_ORDER_LOW,
- appendCrc16Ccitt,
- crc16Ccitt,
- hasValidCrc16Ccitt
- } = require('../../utils/crc.js')
- const PROTOCOL_NAME = 'storage-access'
- const CMD_ERR_MASK = 0x80
- const CMD_CONTROL_FLAG = 0x40
- const CMD_SPECIAL_OP_MASK = 0x3F
- const CMD_WRITE_MASK = 0x08
- const CMD_ADDRESS_MODE_MASK = 0x07
- const CMD_RESERVED_MASK = 0x30
- const CMD_CONTROL = CMD_CONTROL_FLAG
- const CONTROL_RESPONSE_HEADER_LENGTH = 1
- const ADDRESS16_BYTE_LENGTH = 2
- const ADDRESS32_BYTE_LENGTH = 4
- const AREA = {
- CODEINFO: 0x00,
- ADDR32: 0x07,
- DATA: 0x01,
- IDATA: 0x02,
- XDATA: 0x03,
- CODE: 0x04
- }
- const CONTROL_OP = {
- RESET: 0x01
- }
- const AREA_NAMES = {
- [AREA.CODEINFO]: 'CODEINFO',
- [AREA.ADDR32]: 'ADDR32',
- [AREA.DATA]: 'DATA',
- [AREA.IDATA]: 'IDATA',
- [AREA.XDATA]: 'XDATA',
- [AREA.CODE]: 'CODE'
- }
- const AREA_BY_NAME = {
- CODEINFO: AREA.CODEINFO,
- CODE_INFO: AREA.CODEINFO,
- ADDR32: AREA.ADDR32,
- ADDRESS32: AREA.ADDR32,
- DATA: AREA.DATA,
- IDATA: AREA.IDATA,
- XDATA: AREA.XDATA,
- CODE: AREA.CODE
- }
- const EXCEPTION_MESSAGES = {
- 0x01: '非法命令',
- 0x02: '非法区域',
- 0x03: '非法地址',
- 0x04: '非法长度',
- 0x05: '写保护',
- 0x06: '设备忙',
- 0x07: '格式错误',
- 0x08: '访问被拒绝',
- 0x09: '内部错误',
- 0x0A: '对齐错误',
- 0x0B: '范围溢出',
- 0x0C: '不支持的操作'
- }
- const DEFAULT_MAX_FRAME_BYTES = 64
- const UNLIMITED_FRAME_BYTES = 0
- const MAX_UINT16 = 0xFFFF
- const MAX_UINT32 = 0xFFFFFFFF
- const MAX_PAYLOAD_BYTES = MAX_UINT16
- const READ_REQUEST_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
- const READ_REQUEST_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
- const WRITE_REQUEST_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
- const WRITE_REQUEST_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
- const READ_RESPONSE_OVERHEAD_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
- const READ_RESPONSE_OVERHEAD_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
- const WRITE_RESPONSE_LENGTH_16 = 1 + ADDRESS16_BYTE_LENGTH + 2 + 2
- const WRITE_RESPONSE_LENGTH_32 = 1 + ADDRESS32_BYTE_LENGTH + 2 + 2
- const EXCEPTION_RESPONSE_LENGTH = 4
- const CODE_INFO_DESCRIPTOR_ADDRESS = 0
- const CODE_INFO_DESCRIPTOR_BYTE_LENGTH = 11
- const CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH = 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH + 2
- const MEMORY_ENDIAN_MARK_BIG = 0x55AA
- const MEMORY_ENDIAN_MARK_LITTLE = 0xAA55
- const MEMORY_ENDIAN = {
- BIG: 'big',
- LITTLE: 'little'
- }
- const STORAGE_CRC_OPTIONS = {
- byteOrder: BYTE_ORDER_LOW
- }
- const VALID_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
- const MEMORY_AREAS = [AREA.CODEINFO, AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
- const WRITABLE_AREAS = [AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA]
- const RESERVED_AREAS = [0x05, 0x06]
- const readWord = readUint16LE
- const readDword = readUint32LE
- const splitWord = splitUint16LE
- const splitDword = splitUint32LE
- function toByte(value, label) {
- if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
- throw new Error(`${label}必须在 0x00 至 0xFF 之间`)
- }
- return value
- }
- function toWord(value, label) {
- if (!Number.isInteger(value) || value < 0 || value > 0xFFFF) {
- throw new Error(`${label}必须在 0x0000 至 0xFFFF 之间`)
- }
- return value
- }
- function toUint32(value, label) {
- if (!Number.isInteger(value) || value < 0 || value > MAX_UINT32) {
- throw new Error(`${label}必须在 0x00000000 至 0xFFFFFFFF 之间`)
- }
- return value
- }
- function normalizeArea(value) {
- if (typeof value === 'string') {
- const area = AREA_BY_NAME[value.trim().toUpperCase()]
- if (area !== undefined) return area
- }
- const area = toByte(Number(value), '存储区域')
- if (VALID_AREAS.indexOf(area) < 0) {
- throw new Error('存储区域必须为 codeinfo/data/idata/xdata/code/addr32')
- }
- return area
- }
- function isAddress32Area(area) {
- return Number(area) === AREA.ADDR32
- }
- function getAddressFieldByteLength(area) {
- return isAddress32Area(area) ? ADDRESS32_BYTE_LENGTH : ADDRESS16_BYTE_LENGTH
- }
- function getMemoryHeaderLength(area) {
- return 1 + getAddressFieldByteLength(area) + 2
- }
- function getReadRequestLength(area) {
- if (Number(area) === AREA.CODEINFO) return 3
- return getMemoryHeaderLength(area) + 2
- }
- function getWriteRequestOverhead(area) {
- return getMemoryHeaderLength(area) + 2
- }
- function getReadResponseOverhead(area) {
- if (Number(area) === AREA.CODEINFO) return 1 + 2
- return getMemoryHeaderLength(area) + 2
- }
- function getWriteResponseLength(area) {
- return getMemoryHeaderLength(area) + 2
- }
- function isCodeInfoDescriptorArea(area) {
- return Number(area) === AREA.CODEINFO
- }
- function normalizeMemoryArea(value) {
- const area = normalizeArea(value)
- if (MEMORY_AREAS.indexOf(area) < 0) {
- throw new Error('存储访问区域必须为 codeinfo/data/idata/xdata/code/addr32')
- }
- return area
- }
- function toByteLength(value, label = '字节长度', maxPayload = MAX_PAYLOAD_BYTES) {
- const byteLength = toWord(Number(value), label)
- if (byteLength === 0) {
- throw new Error(`${label}必须大于 0`)
- }
- if (maxPayload > 0 && byteLength > maxPayload) {
- throw new Error(`单帧最多访问 ${maxPayload} 字节`)
- }
- return byteLength
- }
- function normalizeDescriptorAddressWidth(value) {
- const numberValue = Number(value)
- if (numberValue === 16 || numberValue === 32) return numberValue
- throw new Error('CodeInfo 描述符地址长度必须为 16 或 32')
- }
- function parseMemoryEndianMark(bytes, offset) {
- const firstByte = readByte(bytes, offset)
- const secondByte = readByte(bytes, offset + 1)
- const marker = ((firstByte << 8) | secondByte) & 0xFFFF
- if (firstByte === 0x55 && secondByte === 0xAA) {
- return {
- marker: MEMORY_ENDIAN_MARK_BIG,
- memoryEndian: MEMORY_ENDIAN.BIG
- }
- }
- if (firstByte === 0xAA && secondByte === 0x55) {
- return {
- marker: MEMORY_ENDIAN_MARK_LITTLE,
- memoryEndian: MEMORY_ENDIAN.LITTLE
- }
- }
- return {
- marker,
- memoryEndian: ''
- }
- }
- function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
- const numberValue = Number(maxFrameBytes)
- if (Number.isFinite(numberValue) && Math.round(numberValue) === UNLIMITED_FRAME_BYTES) return UNLIMITED_FRAME_BYTES
- if (Number.isFinite(numberValue) && numberValue > 0) return Math.round(numberValue)
- return DEFAULT_MAX_FRAME_BYTES
- }
- function resolveDescriptorMaxFrameBytes(configuredMaxFrameBytes, descriptorMaxFrameBytes) {
- const configured = normalizeMaxFrameBytes(configuredMaxFrameBytes)
- const descriptor = normalizeMaxFrameBytes(descriptorMaxFrameBytes)
- if (descriptor === UNLIMITED_FRAME_BYTES) return configured
- if (configured === UNLIMITED_FRAME_BYTES) return descriptor
- return Math.min(configured, descriptor)
- }
- function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
- const frameBytes = normalizeMaxFrameBytes(maxFrameBytes)
- if (frameBytes === UNLIMITED_FRAME_BYTES) return MAX_PAYLOAD_BYTES
- return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
- }
- function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
- return getPayloadLimitFromFrame(maxFrameBytes, getReadResponseOverhead(area))
- }
- function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
- return getPayloadLimitFromFrame(maxFrameBytes, getWriteRequestOverhead(area))
- }
- function buildCommand(area, isWrite = false) {
- const normalizedArea = normalizeMemoryArea(area)
- if (RESERVED_AREAS.indexOf(normalizedArea) >= 0) {
- throw new Error('存储访问区域号 0x05/0x06 暂时保留')
- }
- if (isWrite && WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
- throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
- }
- return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
- }
- function buildSpecialCommand(operation) {
- const op = toByte(Number(operation), '特殊指令') & CMD_SPECIAL_OP_MASK
- if (op <= 0) {
- throw new Error('特殊指令必须在 0x01 至 0x3F 之间')
- }
- return CMD_CONTROL_FLAG | op
- }
- function decodeCommand(command) {
- const cmd = toByte(Number(command), '命令字')
- const sourceCommand = cmd & ~CMD_ERR_MASK
- const isControl = !!(sourceCommand & CMD_CONTROL_FLAG)
- const operation = isControl ? (sourceCommand & CMD_SPECIAL_OP_MASK) : 0
- const area = isControl ? CMD_CONTROL : (sourceCommand & CMD_ADDRESS_MODE_MASK)
- const reservedBits = sourceCommand & CMD_RESERVED_MASK
- return {
- addressBytes: isControl ? 0 : getAddressFieldByteLength(area),
- area,
- command: cmd,
- hasError: !!(cmd & CMD_ERR_MASK),
- hasInvalidSpecialOperation: isControl && operation === 0,
- hasReservedBits: !isControl && reservedBits !== 0,
- isAddress32: !isControl && isAddress32Area(area),
- isControl,
- isWrite: !isControl && !!(sourceCommand & CMD_WRITE_MASK),
- operation,
- sourceCommand
- }
- }
- function hasValidStorageCrc(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < 3) return false
- if (frame.length >= 4) return hasValidCrc16Ccitt(frame, STORAGE_CRC_OPTIONS)
- const expected = crc16Ccitt(frame.slice(0, -2), STORAGE_CRC_OPTIONS)
- const received = readWord(frame, frame.length - 2)
- return expected === received
- }
- function appendStorageCrc(bytes) {
- return appendCrc16Ccitt(bytes, STORAGE_CRC_OPTIONS)
- }
- function buildReadFrame(area, address, byteLength, options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- if (isCodeInfoDescriptorArea(normalizedArea)) {
- return buildCodeInfoDescriptorFrame()
- }
- const addressBytes = getAddressFieldByteLength(normalizedArea)
- const startAddress = addressBytes === ADDRESS32_BYTE_LENGTH
- ? toUint32(Number(address), '内存地址')
- : toWord(Number(address), '内存地址')
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes, normalizedArea)
- const length = toByteLength(Number(byteLength), '读取字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
- const command = buildCommand(normalizedArea, false)
- const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH ? splitDword(startAddress) : splitWord(startAddress)
- const lengthParts = splitWord(toWord(length, '读取字节长度'))
- return appendStorageCrc([command].concat(addressParts, lengthParts))
- }
- function buildWriteFrame(area, address, bytes, options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- if (WRITABLE_AREAS.indexOf(normalizedArea) < 0) {
- throw new Error(`${AREA_NAMES[normalizedArea] || '该'} 区不可写`)
- }
- const addressBytes = getAddressFieldByteLength(normalizedArea)
- const startAddress = addressBytes === ADDRESS32_BYTE_LENGTH
- ? toUint32(Number(address), '内存地址')
- : toWord(Number(address), '内存地址')
- const dataBytes = toByteArray(bytes).map((byte) => toByte(Number(byte), '写入字节'))
- const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes, normalizedArea)
- const length = toByteLength(dataBytes.length, '写入字节长度', maxByteLength || MAX_PAYLOAD_BYTES)
- const command = buildCommand(normalizedArea, true)
- const addressParts = addressBytes === ADDRESS32_BYTE_LENGTH ? splitDword(startAddress) : splitWord(startAddress)
- const lengthParts = splitWord(toWord(length, '写入字节长度'))
- return appendStorageCrc([command].concat(addressParts, lengthParts, dataBytes))
- }
- function buildControlFrame(operation, dataBytes = []) {
- const command = buildSpecialCommand(operation)
- const payload = toByteArray(dataBytes).map((byte) => toByte(Number(byte), '指令数据'))
- return appendStorageCrc([command].concat(payload))
- }
- function buildCodeInfoDescriptorFrame() {
- return appendStorageCrc([buildCommand(AREA.CODEINFO, false)])
- }
- function parseCodeInfoDescriptorBytes(bytes) {
- const dataBytes = toByteArray(bytes)
- if (dataBytes.length < CODE_INFO_DESCRIPTOR_BYTE_LENGTH) {
- throw new Error('CodeInfo 描述符长度无效')
- }
- const endianMark = parseMemoryEndianMark(dataBytes, 9)
- if (!endianMark.memoryEndian) {
- throw new Error('CodeInfo 描述符字节序标记无效')
- }
- return {
- codeInfoAddress: readDword(dataBytes, 0),
- codeInfoAddressWidth: dataBytes[6] & 0xFF,
- codeInfoByteLength: readWord(dataBytes, 4),
- codeInfoDescriptorBytes: dataBytes.slice(0, CODE_INFO_DESCRIPTOR_BYTE_LENGTH),
- codeInfoMaxPacketLength: readWord(dataBytes, 7),
- codeInfoMemoryEndian: endianMark.memoryEndian,
- codeInfoMemoryEndianMark: endianMark.marker
- }
- }
- function formatHex(bytes) {
- return bytesToHex(bytes, ' ')
- }
- function parseStorageAccessResponse(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < 3 || !hasValidStorageCrc(frame)) return null
- const command = frame[0] & 0xFF
- const decoded = decodeCommand(command)
- if (decoded.isControl) return parseStorageControlResponse(frame, decoded)
- if (decoded.hasError) {
- if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
- return {
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- command,
- exceptionCode: frame[1] & 0xFF,
- isException: true,
- isWrite: decoded.isWrite,
- protocol: PROTOCOL_NAME,
- sourceCommand: decoded.sourceCommand
- }
- }
- if (decoded.hasReservedBits) return null
- if (!AREA_NAMES[decoded.area]) return null
- if (isCodeInfoDescriptorArea(decoded.area)) {
- if (decoded.isWrite || frame.length !== CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH) return null
- const dataBytes = frame.slice(1, 1 + CODE_INFO_DESCRIPTOR_BYTE_LENGTH)
- const response = {
- address: CODE_INFO_DESCRIPTOR_ADDRESS,
- addressWidth: 0,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
- command,
- dataBytes,
- isException: false,
- isAddress32: false,
- isWrite: false,
- protocol: PROTOCOL_NAME,
- words: bytesToWordsLE(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0))
- }
- return {
- ...response,
- ...parseCodeInfoDescriptorBytes(dataBytes)
- }
- }
- const addressBytes = decoded.addressBytes
- const headerLength = getMemoryHeaderLength(decoded.area)
- if (decoded.isWrite) {
- if (frame.length !== getWriteResponseLength(decoded.area)) return null
- return {
- address: decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1),
- addressWidth: decoded.isAddress32 ? 32 : 16,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength: readWord(frame, 1 + addressBytes),
- command,
- dataBytes: [],
- isException: false,
- isAddress32: decoded.isAddress32,
- isWrite: true,
- protocol: PROTOCOL_NAME
- }
- }
- if (frame.length < getReadResponseOverhead(decoded.area)) return null
- const address = decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1)
- const byteLength = readWord(frame, 1 + addressBytes)
- const dataStart = headerLength
- const dataEnd = dataStart + byteLength
- if (frame.length !== dataEnd + 2) return null
- const dataBytes = frame.slice(dataStart, dataEnd)
- const response = {
- address,
- addressWidth: decoded.isAddress32 ? 32 : 16,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength,
- command,
- dataBytes,
- isException: false,
- isAddress32: decoded.isAddress32,
- isWrite: false,
- protocol: PROTOCOL_NAME,
- words: bytesToWordsLE(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0))
- }
- return response
- }
- function parseStorageControlResponse(frame, decoded) {
- if (decoded.hasInvalidSpecialOperation) return null
- if (decoded.hasError) {
- if (frame.length !== EXCEPTION_RESPONSE_LENGTH) return null
- return {
- area: CMD_CONTROL,
- areaName: 'CONTROL',
- command: frame[0] & 0xFF,
- exceptionCode: frame[1] & 0xFF,
- isControl: true,
- isException: true,
- isWrite: false,
- operation: decoded.operation,
- protocol: PROTOCOL_NAME,
- sourceCommand: decoded.sourceCommand
- }
- }
- if (frame.length < CONTROL_RESPONSE_HEADER_LENGTH + 2) return null
- const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
- const dataEnd = frame.length - 2
- const byteLength = Math.max(0, dataEnd - dataStart)
- const dataBytes = frame.slice(dataStart, dataEnd)
- return {
- area: CMD_CONTROL,
- areaName: 'CONTROL',
- byteLength,
- command: frame[0] & 0xFF,
- dataBytes,
- isControl: true,
- isException: false,
- isWrite: false,
- operation: decoded.operation,
- protocol: PROTOCOL_NAME
- }
- }
- function parseStorageAccessRequest(bytes) {
- const frame = toByteArray(bytes)
- if (frame.length < 3 || !hasValidStorageCrc(frame)) return null
- const command = frame[0] & 0xFF
- const decoded = decodeCommand(command)
- if (decoded.isControl) return parseStorageControlRequest(frame, decoded)
- if (decoded.hasError) return null
- if (decoded.hasReservedBits) return null
- if (!AREA_NAMES[decoded.area]) return null
- if (isCodeInfoDescriptorArea(decoded.area)) {
- if (decoded.isWrite || frame.length !== getReadRequestLength(decoded.area)) return null
- return {
- address: CODE_INFO_DESCRIPTOR_ADDRESS,
- addressWidth: 0,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength: CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
- command,
- dataBytes: [],
- isAddress32: false,
- isWrite: false,
- kind: 'raw-hex',
- operation: 'read',
- protocol: PROTOCOL_NAME,
- quantity: CODE_INFO_DESCRIPTOR_BYTE_LENGTH
- }
- }
- if (frame.length < READ_REQUEST_LENGTH_16) return null
- const addressBytes = decoded.addressBytes
- const headerLength = getMemoryHeaderLength(decoded.area)
- if (frame.length < headerLength + 2) return null
- const address = decoded.isAddress32 ? readDword(frame, 1) : readWord(frame, 1)
- const byteLength = readWord(frame, 1 + addressBytes)
- const expectedLength = decoded.isWrite
- ? headerLength + byteLength + 2
- : headerLength + 2
- if (byteLength <= 0 || frame.length !== expectedLength) return null
- return {
- address,
- addressWidth: decoded.isAddress32 ? 32 : 16,
- area: decoded.area,
- areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
- byteLength,
- command,
- dataBytes: decoded.isWrite ? frame.slice(headerLength, headerLength + byteLength) : [],
- isAddress32: decoded.isAddress32,
- isWrite: decoded.isWrite,
- kind: 'raw-hex',
- operation: decoded.isWrite ? 'write' : 'read',
- protocol: PROTOCOL_NAME,
- quantity: byteLength
- }
- }
- function parseStorageControlRequest(frame, decoded = decodeCommand(frame && frame[0])) {
- if (frame.length < 3 || decoded.hasInvalidSpecialOperation) return null
- const dataStart = CONTROL_RESPONSE_HEADER_LENGTH
- const dataEnd = frame.length - 2
- const byteLength = Math.max(0, dataEnd - dataStart)
- return {
- area: CMD_CONTROL,
- areaName: 'CONTROL',
- byteLength,
- command: frame[0] & 0xFF,
- dataBytes: frame.slice(dataStart, dataEnd),
- isControl: true,
- isWrite: false,
- kind: 'storage-control',
- operation: decoded.operation,
- protocol: PROTOCOL_NAME
- }
- }
- function getExpectedResponseLength(expected, responseCommand, responseBytes = []) {
- if (!expected) return 0
- const command = Number(responseCommand) & 0xFF
- if (command === (expected.command | CMD_ERR_MASK)) return EXCEPTION_RESPONSE_LENGTH
- if (command !== expected.command) return 0
- if (expected.isControl) {
- if (responseBytes.length < CONTROL_RESPONSE_HEADER_LENGTH) return 0
- return CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
- }
- if (expected.operation === 'write' || expected.isWrite) {
- return getWriteResponseLength(expected.area)
- }
- if (isCodeInfoDescriptorArea(expected.area)) {
- return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
- }
- const headerLength = getMemoryHeaderLength(expected.area)
- if (responseBytes.length < headerLength) return 0
- return headerLength + readWord(responseBytes, 1 + getAddressFieldByteLength(expected.area)) + 2
- }
- function isExpectedResponse(response, expected) {
- if (!response || !expected) return false
- const sourceCommand = response.isException ? response.sourceCommand : response.command
- if (sourceCommand !== expected.command) return false
- if (expected.isControl) {
- if (!response.isControl) return false
- if (response.isException) return true
- if (response.operation !== expected.operation) return false
- return true
- }
- if (!response.isException && response.area !== expected.area) return false
- if (response.isException) return true
- if (response.address !== expected.address) return false
- if (response.byteLength !== expected.byteLength) return false
- if (!response.isWrite && (!Array.isArray(response.dataBytes) || response.dataBytes.length !== expected.byteLength)) return false
- return true
- }
- function getExceptionText(code) {
- return EXCEPTION_MESSAGES[code] || '未知异常'
- }
- function formatExceptionMessage(response) {
- const sourceCommand = response && response.sourceCommand
- const exceptionCode = response && response.exceptionCode
- const exceptionText = getExceptionText(exceptionCode)
- return `设备返回异常帧:命令 0x${padHex(sourceCommand, 2)},异常码 0x${padHex(exceptionCode, 2)}(${exceptionText})`
- }
- function getReadBufferHint(expected) {
- if (!expected) return 0
- if (expected.isControl) return expected.responseBufferHint || CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
- if (expected.operation === 'write' || expected.isWrite) {
- return getWriteResponseLength(expected.area)
- }
- if (isCodeInfoDescriptorArea(expected.area)) {
- return CODE_INFO_DESCRIPTOR_RESPONSE_LENGTH
- }
- return getReadResponseOverhead(expected.area) + Number(expected.byteLength || expected.quantity || 0)
- }
- function alignResponseBuffer(buffer, expected) {
- if (!Array.isArray(buffer) || !buffer.length || !expected) return
- const expectedCommands = [expected.command, expected.command | CMD_ERR_MASK]
- let matchIndex = -1
- for (let index = 0; index < buffer.length; index += 1) {
- if (expectedCommands.indexOf(buffer[index]) < 0) continue
- matchIndex = index
- break
- }
- if (matchIndex > 0) {
- buffer.splice(0, matchIndex)
- } else if (matchIndex < 0 && buffer.length > 1) {
- buffer.splice(0, buffer.length - 1)
- }
- }
- function readResponseFromBuffer(buffer, expected, options = {}) {
- if (!Array.isArray(buffer) || !buffer.length || !expected) {
- return {
- status: 'pending'
- }
- }
- alignResponseBuffer(buffer, expected)
- while (buffer.length >= 1) {
- const responseCommand = buffer[0]
- const responseLength = getExpectedResponseLength(expected, responseCommand, buffer)
- if (!responseLength) {
- return {
- status: 'pending'
- }
- }
- const frameLimit = normalizeMaxFrameBytes(
- options.maxFrameBytes === undefined ? expected.maxFrameBytes : options.maxFrameBytes
- )
- if (frameLimit > 0 && responseLength > frameLimit) {
- return {
- frameLimit,
- responseLength,
- status: 'frame-too-long'
- }
- }
- if (buffer.length < responseLength) {
- return {
- status: 'pending'
- }
- }
- const frameBytes = buffer.slice(0, responseLength)
- const response = parseStorageAccessResponse(frameBytes)
- if (!response) {
- return {
- frameBytes,
- status: 'invalid'
- }
- }
- if (!isExpectedResponse(response, expected)) {
- buffer.shift()
- alignResponseBuffer(buffer, expected)
- continue
- }
- if (response.isException) {
- return {
- message: formatExceptionMessage(response),
- response,
- status: 'exception'
- }
- }
- buffer.splice(0, responseLength)
- return {
- response,
- status: 'complete'
- }
- }
- return {
- status: 'pending'
- }
- }
- function createExpected(area, address, byteLength, isWrite, kind, options = {}) {
- const normalizedArea = normalizeMemoryArea(area)
- const command = buildCommand(normalizedArea, isWrite)
- return {
- address,
- addressWidth: isAddress32Area(normalizedArea) ? 32 : 16,
- area: normalizedArea,
- byteLength,
- command,
- isAddress32: isAddress32Area(normalizedArea),
- isWrite,
- kind,
- operation: isWrite ? 'write' : 'read',
- protocol: PROTOCOL_NAME,
- quantity: byteLength
- }
- }
- function createControlExpected(operation, kind = 'storage-control', options = {}) {
- const op = toByte(Number(operation), '特殊指令')
- const command = buildSpecialCommand(op)
- return {
- area: CMD_CONTROL,
- byteLength: 0,
- command,
- expectedByteLength: Number(options.expectedByteLength) || 0,
- isControl: true,
- isWrite: false,
- kind,
- operation: op,
- protocol: PROTOCOL_NAME,
- responseBufferHint: CONTROL_RESPONSE_HEADER_LENGTH + (Number(options.expectedByteLength) || 0) + 2
- }
- }
- function formatAddress(value) {
- return formatHexNumber(value, 1).replace(/^0+(?=[0-9A-F])/, '')
- }
- function getChunkLabel(label, chunks, chunk) {
- if (!label || chunks.length <= 1) return label
- return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}`
- }
- function splitQuantity(startAddress, quantity, maxQuantity) {
- const chunks = []
- let address = Number(startAddress) || 0
- let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
- const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1))
- while (remaining > 0) {
- const chunkQuantity = Math.min(remaining, chunkLimit)
- chunks.push({
- address,
- quantity: chunkQuantity
- })
- address += chunkQuantity
- remaining -= chunkQuantity
- }
- return chunks
- }
- function getReadChunks(startAddress, byteLength, options = {}) {
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes, options.area)
- return splitQuantity(startAddress, byteLength, maxByteLength || byteLength)
- }
- function getWriteChunks(startAddress, bytes, options = {}) {
- const sourceBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF)
- const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes, options.area)
- const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
- let offset = 0
- return chunks.map((chunk) => {
- const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity)
- offset += chunk.quantity
- return {
- ...chunk,
- dataBytes
- }
- })
- }
- const response = {
- createControlExpected,
- createExpected,
- formatExceptionMessage,
- getExceptionText,
- getExpectedResponseLength,
- getReadBufferHint,
- isExpectedResponse,
- parseStorageAccessRequest,
- parseStorageAccessResponse,
- readResponseFromBuffer
- }
- module.exports = {
- AREA,
- AREA_BY_NAME,
- AREA_NAMES,
- ADDRESS16_BYTE_LENGTH,
- ADDRESS32_BYTE_LENGTH,
- CMD_CONTROL,
- CMD_CONTROL_FLAG,
- CMD_SPECIAL_OP_MASK,
- CMD_ADDRESS_MODE_MASK,
- CMD_ERR_MASK,
- CMD_RESERVED_MASK,
- CMD_WRITE_MASK,
- CONTROL_OP,
- CONTROL_RESPONSE_HEADER_LENGTH,
- DEFAULT_MAX_FRAME_BYTES,
- EXCEPTION_MESSAGES,
- EXCEPTION_RESPONSE_LENGTH,
- CODE_INFO_DESCRIPTOR_ADDRESS,
- CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
- MAX_PAYLOAD_BYTES,
- MEMORY_ENDIAN,
- MEMORY_ENDIAN_MARK_BIG,
- MEMORY_ENDIAN_MARK_LITTLE,
- PROTOCOL_NAME,
- READ_REQUEST_LENGTH_16,
- READ_REQUEST_LENGTH_32,
- READ_RESPONSE_OVERHEAD_16,
- READ_RESPONSE_OVERHEAD_32,
- STORAGE_CRC_OPTIONS,
- UNLIMITED_FRAME_BYTES,
- WRITE_REQUEST_OVERHEAD_16,
- WRITE_REQUEST_OVERHEAD_32,
- WRITE_RESPONSE_LENGTH_16,
- WRITE_RESPONSE_LENGTH_32,
- appendStorageCrc,
- buildCommand,
- buildCodeInfoDescriptorFrame,
- buildControlFrame,
- buildReadFrame,
- buildSpecialCommand,
- buildWriteFrame,
- createControlExpected,
- createExpected,
- decodeCommand,
- formatHex,
- getChunkLabel,
- getReadChunks,
- getWriteChunks,
- getMaxReadByteLength,
- getMaxWriteByteLength,
- hasValidStorageCrc,
- normalizeDescriptorAddressWidth,
- normalizeArea,
- normalizeMaxFrameBytes,
- normalizeMemoryArea,
- parseCodeInfoDescriptorBytes,
- resolveDescriptorMaxFrameBytes,
- response,
- splitDword,
- splitQuantity,
- splitWord,
- toByte,
- toByteLength,
- toUint32,
- toWord
- }
|