|
|
@@ -11,39 +11,58 @@ const {
|
|
|
crc16Ccitt,
|
|
|
hasValidCrc16Ccitt
|
|
|
} = require('../../utils/crc.js')
|
|
|
-const transport = require('../../transport/ble-core.js')
|
|
|
|
|
|
const PROTOCOL_NAME = 'storage-access'
|
|
|
|
|
|
const CMD_ERR_MASK = 0x80
|
|
|
-const CMD_WRITE_MASK = 0x40
|
|
|
-const CMD_AREA_MASK = 0x3F
|
|
|
-const CMD_INFO = 0x0F
|
|
|
-const INFO_DATA_BYTE_LENGTH = 4
|
|
|
+const CMD_CONTROL_FLAG = 0x40
|
|
|
+const CMD_WRITE_MASK = 0x08
|
|
|
+const CMD_ADDRESS_MODE_MASK = 0x07
|
|
|
+const CMD_RESERVED_MASK = 0x30
|
|
|
+const CMD_CONTROL = 0x4F
|
|
|
+const CONTROL_RESPONSE_HEADER_LENGTH = 3
|
|
|
+const ADDRESS16_BYTE_LENGTH = 2
|
|
|
+const ADDRESS32_BYTE_LENGTH = 4
|
|
|
|
|
|
const AREA = {
|
|
|
+ ADDR32: 0x07,
|
|
|
DATA: 0x01,
|
|
|
IDATA: 0x02,
|
|
|
XDATA: 0x03,
|
|
|
- CODE: 0x04,
|
|
|
- INFO: 0x0F
|
|
|
+ CODE: 0x04
|
|
|
+}
|
|
|
+
|
|
|
+const CONTROL_OP = {
|
|
|
+ RESET: 0x01,
|
|
|
+ START: 0x02,
|
|
|
+ STOP: 0x03,
|
|
|
+ SET_CONTROL_REF: 0x04,
|
|
|
+ READ_CODE_INFO_DESCRIPTOR: 0x05
|
|
|
+}
|
|
|
+
|
|
|
+const CONTROL_STATUS_MESSAGES = {
|
|
|
+ 0x00: '成功',
|
|
|
+ 0x01: '不支持的指令',
|
|
|
+ 0x02: '参数无效',
|
|
|
+ 0x03: '设备忙',
|
|
|
+ 0x04: '执行失败'
|
|
|
}
|
|
|
|
|
|
const AREA_NAMES = {
|
|
|
+ [AREA.ADDR32]: 'ADDR32',
|
|
|
[AREA.DATA]: 'DATA',
|
|
|
[AREA.IDATA]: 'IDATA',
|
|
|
[AREA.XDATA]: 'XDATA',
|
|
|
- [AREA.CODE]: 'CODE',
|
|
|
- [AREA.INFO]: 'INFO'
|
|
|
+ [AREA.CODE]: 'CODE'
|
|
|
}
|
|
|
|
|
|
const AREA_BY_NAME = {
|
|
|
+ ADDR32: AREA.ADDR32,
|
|
|
+ ADDRESS32: AREA.ADDR32,
|
|
|
DATA: AREA.DATA,
|
|
|
IDATA: AREA.IDATA,
|
|
|
XDATA: AREA.XDATA,
|
|
|
- CODE: AREA.CODE,
|
|
|
- INFO: AREA.INFO,
|
|
|
- SYNC: AREA.INFO
|
|
|
+ CODE: AREA.CODE
|
|
|
}
|
|
|
|
|
|
const EXCEPTION_MESSAGES = {
|
|
|
@@ -62,22 +81,34 @@ const EXCEPTION_MESSAGES = {
|
|
|
}
|
|
|
|
|
|
const DEFAULT_MAX_FRAME_BYTES = 64
|
|
|
-const MAX_PAYLOAD_BYTES = 256
|
|
|
const UNLIMITED_FRAME_BYTES = 0
|
|
|
-
|
|
|
-const READ_REQUEST_LENGTH = 7
|
|
|
-const WRITE_REQUEST_OVERHEAD = 7
|
|
|
-const READ_RESPONSE_OVERHEAD = 7
|
|
|
-const WRITE_RESPONSE_LENGTH = 7
|
|
|
+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 INFO_REQUEST_LENGTH = READ_REQUEST_LENGTH
|
|
|
-const INFO_RESPONSE_LENGTH = READ_RESPONSE_OVERHEAD + INFO_DATA_BYTE_LENGTH
|
|
|
+const CODE_INFO_DESCRIPTOR_BYTE_LENGTH = 11
|
|
|
+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_HIGH
|
|
|
}
|
|
|
-const VALID_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE, AREA.INFO]
|
|
|
-const MEMORY_AREAS = [AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
|
|
|
+const VALID_AREAS = [AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
|
|
|
+const MEMORY_AREAS = [AREA.ADDR32, AREA.DATA, AREA.IDATA, AREA.XDATA, AREA.CODE]
|
|
|
+const RESERVED_AREAS = [0x00, 0x05, 0x06]
|
|
|
|
|
|
function toByte(value, label) {
|
|
|
if (!Number.isInteger(value) || value < 0 || value > 0xFF) {
|
|
|
@@ -95,24 +126,68 @@ function toWord(value, label) {
|
|
|
return value
|
|
|
}
|
|
|
|
|
|
+function toInt16Word(value, label) {
|
|
|
+ if (!Number.isInteger(value) || value < -0x8000 || value > 0x7FFF) {
|
|
|
+ throw new Error(`${label}必须在 -32768 至 32767 之间`)
|
|
|
+ }
|
|
|
+
|
|
|
+ return value & 0xFFFF
|
|
|
+}
|
|
|
+
|
|
|
+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) return area
|
|
|
+ if (area !== undefined) return area
|
|
|
}
|
|
|
|
|
|
const area = toByte(Number(value), '存储区域')
|
|
|
if (VALID_AREAS.indexOf(area) < 0) {
|
|
|
- throw new Error('存储区域必须为 data/idata/xdata/code/info')
|
|
|
+ throw new Error('存储区域必须为 addr32/data/idata/xdata/code')
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
+ return getMemoryHeaderLength(area) + 2
|
|
|
+}
|
|
|
+
|
|
|
+function getWriteRequestOverhead(area) {
|
|
|
+ return getMemoryHeaderLength(area) + 2
|
|
|
+}
|
|
|
+
|
|
|
+function getReadResponseOverhead(area) {
|
|
|
+ return getMemoryHeaderLength(area) + 2
|
|
|
+}
|
|
|
+
|
|
|
+function getWriteResponseLength(area) {
|
|
|
+ return getMemoryHeaderLength(area) + 2
|
|
|
+}
|
|
|
+
|
|
|
function normalizeMemoryArea(value) {
|
|
|
const area = normalizeArea(value)
|
|
|
if (MEMORY_AREAS.indexOf(area) < 0) {
|
|
|
- throw new Error('存储读写区域必须为 data/idata/xdata/code')
|
|
|
+ throw new Error('存储读写区域必须为 addr32/data/idata/xdata/code')
|
|
|
}
|
|
|
|
|
|
return area
|
|
|
@@ -134,10 +209,58 @@ function splitWord(value) {
|
|
|
return [(value >> 8) & 0xFF, value & 0xFF]
|
|
|
}
|
|
|
|
|
|
+function splitDword(value) {
|
|
|
+ const normalizedValue = Number(value) >>> 0
|
|
|
+
|
|
|
+ return [
|
|
|
+ (normalizedValue >>> 24) & 0xFF,
|
|
|
+ (normalizedValue >>> 16) & 0xFF,
|
|
|
+ (normalizedValue >>> 8) & 0xFF,
|
|
|
+ normalizedValue & 0xFF
|
|
|
+ ]
|
|
|
+}
|
|
|
+
|
|
|
function readWord(bytes, offset) {
|
|
|
return (((bytes[offset] || 0) << 8) | (bytes[offset + 1] || 0)) & 0xFFFF
|
|
|
}
|
|
|
|
|
|
+function readDword(bytes, offset) {
|
|
|
+ return (
|
|
|
+ ((bytes[offset] || 0) * 0x1000000)
|
|
|
+ + (((bytes[offset + 1] || 0) << 16) >>> 0)
|
|
|
+ + (((bytes[offset + 2] || 0) << 8) >>> 0)
|
|
|
+ + (bytes[offset + 3] || 0)
|
|
|
+ ) >>> 0
|
|
|
+}
|
|
|
+
|
|
|
+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 marker = readWord(bytes, offset)
|
|
|
+ if (marker === MEMORY_ENDIAN_MARK_BIG) {
|
|
|
+ return {
|
|
|
+ marker,
|
|
|
+ memoryEndian: MEMORY_ENDIAN.BIG
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (marker === MEMORY_ENDIAN_MARK_LITTLE) {
|
|
|
+ return {
|
|
|
+ marker,
|
|
|
+ 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
|
|
|
@@ -146,6 +269,15 @@ function normalizeMaxFrameBytes(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
|
|
|
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
|
|
|
@@ -153,28 +285,41 @@ function getPayloadLimitFromFrame(maxFrameBytes, overhead) {
|
|
|
return Math.max(0, Math.min(MAX_PAYLOAD_BYTES, frameBytes - overhead))
|
|
|
}
|
|
|
|
|
|
-function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
|
|
|
- return getPayloadLimitFromFrame(maxFrameBytes, READ_RESPONSE_OVERHEAD)
|
|
|
+function getMaxReadByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
|
|
|
+ return getPayloadLimitFromFrame(maxFrameBytes, getReadResponseOverhead(area))
|
|
|
}
|
|
|
|
|
|
-function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES) {
|
|
|
- return getPayloadLimitFromFrame(maxFrameBytes, WRITE_REQUEST_OVERHEAD)
|
|
|
+function getMaxWriteByteLength(maxFrameBytes = DEFAULT_MAX_FRAME_BYTES, area = AREA.ADDR32) {
|
|
|
+ return getPayloadLimitFromFrame(maxFrameBytes, getWriteRequestOverhead(area))
|
|
|
}
|
|
|
|
|
|
function buildCommand(area, isWrite = false) {
|
|
|
const normalizedArea = isWrite ? normalizeMemoryArea(area) : normalizeArea(area)
|
|
|
|
|
|
+ if (RESERVED_AREAS.indexOf(normalizedArea) >= 0) {
|
|
|
+ throw new Error('存储访问区域号 0x00/0x05/0x06 暂时保留')
|
|
|
+ }
|
|
|
+
|
|
|
return (isWrite ? CMD_WRITE_MASK : 0x00) | normalizedArea
|
|
|
}
|
|
|
|
|
|
function decodeCommand(command) {
|
|
|
const cmd = toByte(Number(command), '命令字')
|
|
|
+ const sourceCommand = cmd & ~CMD_ERR_MASK
|
|
|
+ const isControl = sourceCommand === CMD_CONTROL
|
|
|
+ const area = isControl ? CMD_CONTROL : (sourceCommand & CMD_ADDRESS_MODE_MASK)
|
|
|
+ const reservedBits = sourceCommand & CMD_RESERVED_MASK
|
|
|
|
|
|
return {
|
|
|
- area: cmd & CMD_AREA_MASK,
|
|
|
+ addressBytes: isControl ? 0 : getAddressFieldByteLength(area),
|
|
|
+ area,
|
|
|
command: cmd,
|
|
|
hasError: !!(cmd & CMD_ERR_MASK),
|
|
|
- isWrite: !!(cmd & CMD_WRITE_MASK)
|
|
|
+ hasReservedBits: !isControl && reservedBits !== 0,
|
|
|
+ isAddress32: !isControl && isAddress32Area(area),
|
|
|
+ isControl,
|
|
|
+ isWrite: !isControl && !!(sourceCommand & CMD_WRITE_MASK),
|
|
|
+ sourceCommand
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -193,16 +338,18 @@ function appendStorageCrc(bytes) {
|
|
|
}
|
|
|
|
|
|
function buildReadFrame(area, address, byteLength, options = {}) {
|
|
|
- const command = buildCommand(area, false)
|
|
|
- const startAddress = toWord(Number(address), '内存地址')
|
|
|
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
|
|
|
+ const normalizedArea = normalizeMemoryArea(area)
|
|
|
+ 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(splitWord(startAddress), splitWord(length)))
|
|
|
-}
|
|
|
-
|
|
|
-function buildInfoFrame(address = 0, byteLength = INFO_DATA_BYTE_LENGTH) {
|
|
|
- return buildReadFrame(AREA.INFO, address, byteLength)
|
|
|
+ return appendStorageCrc([command].concat(addressParts, lengthParts))
|
|
|
}
|
|
|
|
|
|
function buildWriteFrame(area, address, bytes, options = {}) {
|
|
|
@@ -211,13 +358,33 @@ function buildWriteFrame(area, address, bytes, options = {}) {
|
|
|
throw new Error('code 区暂不支持写入')
|
|
|
}
|
|
|
|
|
|
- const command = buildCommand(normalizedArea, true)
|
|
|
- const startAddress = toWord(Number(address), '内存地址')
|
|
|
+ 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)
|
|
|
+ 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(splitWord(startAddress), splitWord(length), dataBytes))
|
|
|
+ return appendStorageCrc([command].concat(addressParts, lengthParts, dataBytes))
|
|
|
+}
|
|
|
+
|
|
|
+function buildControlFrame(operation, dataBytes = []) {
|
|
|
+ const op = toByte(Number(operation), '特殊指令')
|
|
|
+ const payload = toByteArray(dataBytes).map((byte) => toByte(Number(byte), '指令数据'))
|
|
|
+
|
|
|
+ return appendStorageCrc([CMD_CONTROL, op].concat(payload))
|
|
|
+}
|
|
|
+
|
|
|
+function buildControlReferenceFrame(referenceValue) {
|
|
|
+ return buildControlFrame(CONTROL_OP.SET_CONTROL_REF, splitWord(toInt16Word(Number(referenceValue), '控制参考值')))
|
|
|
+}
|
|
|
+
|
|
|
+function buildCodeInfoDescriptorFrame() {
|
|
|
+ return buildControlFrame(CONTROL_OP.READ_CODE_INFO_DESCRIPTOR)
|
|
|
}
|
|
|
|
|
|
function formatHex(bytes) {
|
|
|
@@ -230,6 +397,7 @@ function parseStorageAccessResponse(bytes) {
|
|
|
|
|
|
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
|
|
|
@@ -242,88 +410,151 @@ function parseStorageAccessResponse(bytes) {
|
|
|
isException: true,
|
|
|
isWrite: decoded.isWrite,
|
|
|
protocol: PROTOCOL_NAME,
|
|
|
- sourceCommand: command & ~CMD_ERR_MASK
|
|
|
+ sourceCommand: decoded.sourceCommand
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (decoded.hasReservedBits) return null
|
|
|
if (!AREA_NAMES[decoded.area]) return null
|
|
|
+ const addressBytes = decoded.addressBytes
|
|
|
+ const headerLength = getMemoryHeaderLength(decoded.area)
|
|
|
|
|
|
if (decoded.isWrite) {
|
|
|
- if (decoded.area === AREA.INFO) return null
|
|
|
- if (frame.length !== WRITE_RESPONSE_LENGTH) return null
|
|
|
+ if (frame.length !== getWriteResponseLength(decoded.area)) return null
|
|
|
|
|
|
return {
|
|
|
- address: readWord(frame, 1),
|
|
|
+ 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, 3),
|
|
|
+ byteLength: readWord(frame, 1 + addressBytes),
|
|
|
command,
|
|
|
dataBytes: [],
|
|
|
isException: false,
|
|
|
+ isAddress32: decoded.isAddress32,
|
|
|
isWrite: true,
|
|
|
protocol: PROTOCOL_NAME
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (frame.length < READ_RESPONSE_OVERHEAD) return null
|
|
|
+ if (frame.length < getReadResponseOverhead(decoded.area)) return null
|
|
|
|
|
|
- const byteLength = readWord(frame, 3)
|
|
|
- const dataStart = 5
|
|
|
+ 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)
|
|
|
|
|
|
return {
|
|
|
- address: readWord(frame, 1),
|
|
|
+ address,
|
|
|
+ addressWidth: decoded.isAddress32 ? 32 : 16,
|
|
|
area: decoded.area,
|
|
|
areaName: AREA_NAMES[decoded.area] || 'UNKNOWN',
|
|
|
byteLength,
|
|
|
command,
|
|
|
dataBytes,
|
|
|
isException: false,
|
|
|
- isInfo: decoded.area === AREA.INFO,
|
|
|
+ isAddress32: decoded.isAddress32,
|
|
|
isWrite: false,
|
|
|
protocol: PROTOCOL_NAME,
|
|
|
- words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0)),
|
|
|
- ...(decoded.area === AREA.INFO && dataBytes.length >= INFO_DATA_BYTE_LENGTH
|
|
|
- ? {
|
|
|
- codeInfoAddress: readWord(frame, 5),
|
|
|
- codeInfoByteLength: readWord(frame, 7),
|
|
|
- infoBytes: dataBytes.slice(0, INFO_DATA_BYTE_LENGTH)
|
|
|
- }
|
|
|
+ words: bytesToWords(dataBytes.length % 2 === 0 ? dataBytes : dataBytes.concat(0))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function parseStorageControlResponse(frame, decoded) {
|
|
|
+ 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,
|
|
|
+ protocol: PROTOCOL_NAME,
|
|
|
+ sourceCommand: decoded.sourceCommand
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (frame.length < CONTROL_RESPONSE_HEADER_LENGTH + 2) return null
|
|
|
+
|
|
|
+ const operation = frame[1] & 0xFF
|
|
|
+ const status = frame[2] & 0xFF
|
|
|
+ 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,
|
|
|
+ controlStatus: status,
|
|
|
+ controlStatusText: CONTROL_STATUS_MESSAGES[status] || '未知状态',
|
|
|
+ dataBytes,
|
|
|
+ isControl: true,
|
|
|
+ isException: false,
|
|
|
+ isWrite: false,
|
|
|
+ operation,
|
|
|
+ protocol: PROTOCOL_NAME,
|
|
|
+ ...(operation === CONTROL_OP.READ_CODE_INFO_DESCRIPTOR && dataBytes.length >= CODE_INFO_DESCRIPTOR_BYTE_LENGTH
|
|
|
+ ? (() => {
|
|
|
+ const endian = parseMemoryEndianMark(dataBytes, 7)
|
|
|
+
|
|
|
+ return {
|
|
|
+ codeInfoAddress: readDword(dataBytes, 0),
|
|
|
+ codeInfoByteLength: readWord(dataBytes, 4),
|
|
|
+ codeInfoAddressWidth: dataBytes[6] & 0xFF,
|
|
|
+ codeInfoDescriptorBytes: dataBytes.slice(0, CODE_INFO_DESCRIPTOR_BYTE_LENGTH),
|
|
|
+ codeInfoMaxPacketLength: readWord(dataBytes, 9),
|
|
|
+ codeInfoMemoryEndian: endian.memoryEndian,
|
|
|
+ codeInfoMemoryEndianMark: endian.marker
|
|
|
+ }
|
|
|
+ })()
|
|
|
: {})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function parseStorageAccessRequest(bytes) {
|
|
|
const frame = toByteArray(bytes)
|
|
|
- if (frame.length < INFO_REQUEST_LENGTH || !hasValidStorageCrc(frame)) return null
|
|
|
+ if (frame.length < 4 || !hasValidStorageCrc(frame)) return null
|
|
|
|
|
|
const command = frame[0] & 0xFF
|
|
|
- if (frame.length < READ_REQUEST_LENGTH) return null
|
|
|
|
|
|
const decoded = decodeCommand(command)
|
|
|
+ if (decoded.isControl) return parseStorageControlRequest(frame)
|
|
|
+ if (frame.length < READ_REQUEST_LENGTH_16) return null
|
|
|
if (decoded.hasError) return null
|
|
|
+ if (decoded.hasReservedBits) return null
|
|
|
if (!AREA_NAMES[decoded.area]) return null
|
|
|
- if (decoded.area === AREA.INFO && decoded.isWrite) return null
|
|
|
|
|
|
- const address = readWord(frame, 1)
|
|
|
- const byteLength = readWord(frame, 3)
|
|
|
+ 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
|
|
|
- ? WRITE_REQUEST_OVERHEAD + byteLength
|
|
|
- : READ_REQUEST_LENGTH
|
|
|
+ ? 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(5, 5 + byteLength) : [],
|
|
|
- isInfo: decoded.area === AREA.INFO,
|
|
|
+ dataBytes: decoded.isWrite ? frame.slice(headerLength, headerLength + byteLength) : [],
|
|
|
+ isAddress32: decoded.isAddress32,
|
|
|
isWrite: decoded.isWrite,
|
|
|
kind: 'raw-hex',
|
|
|
operation: decoded.isWrite ? 'write' : 'read',
|
|
|
@@ -332,25 +563,63 @@ function parseStorageAccessRequest(bytes) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function parseStorageControlRequest(frame) {
|
|
|
+ if (frame.length < 4) return null
|
|
|
+
|
|
|
+ const operation = frame[1] & 0xFF
|
|
|
+ const dataStart = 2
|
|
|
+ const dataEnd = frame.length - 2
|
|
|
+ const byteLength = Math.max(0, dataEnd - dataStart)
|
|
|
+
|
|
|
+ return {
|
|
|
+ area: CMD_CONTROL,
|
|
|
+ areaName: 'CONTROL',
|
|
|
+ byteLength,
|
|
|
+ command: CMD_CONTROL,
|
|
|
+ dataBytes: frame.slice(dataStart, dataEnd),
|
|
|
+ isControl: true,
|
|
|
+ isWrite: false,
|
|
|
+ kind: 'storage-control',
|
|
|
+ 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
|
|
|
+ const status = responseBytes[2] & 0xFF
|
|
|
+ if (status !== 0) return CONTROL_RESPONSE_HEADER_LENGTH + 2
|
|
|
+
|
|
|
+ return CONTROL_RESPONSE_HEADER_LENGTH + Number(expected.expectedByteLength || 0) + 2
|
|
|
+ }
|
|
|
+
|
|
|
if (expected.operation === 'write' || expected.isWrite) {
|
|
|
- return WRITE_RESPONSE_LENGTH
|
|
|
+ return getWriteResponseLength(expected.area)
|
|
|
}
|
|
|
|
|
|
- if (responseBytes.length < 5) return 0
|
|
|
+ const headerLength = getMemoryHeaderLength(expected.area)
|
|
|
+ if (responseBytes.length < headerLength) return 0
|
|
|
|
|
|
- return READ_RESPONSE_OVERHEAD + readWord(responseBytes, 3)
|
|
|
+ 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
|
|
|
@@ -374,9 +643,12 @@ function formatExceptionMessage(response) {
|
|
|
|
|
|
function getReadBufferHint(expected) {
|
|
|
if (!expected) return 0
|
|
|
- if (expected.operation === 'write' || expected.isWrite) return WRITE_RESPONSE_LENGTH
|
|
|
+ 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)
|
|
|
+ }
|
|
|
|
|
|
- return READ_RESPONSE_OVERHEAD + Number(expected.byteLength || expected.quantity || 0)
|
|
|
+ return getReadResponseOverhead(expected.area) + Number(expected.byteLength || expected.quantity || 0)
|
|
|
}
|
|
|
|
|
|
function alignResponseBuffer(buffer, expected) {
|
|
|
@@ -470,15 +742,17 @@ function readResponseFromBuffer(buffer, expected, options = {}) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function createExpected(area, address, byteLength, isWrite, kind) {
|
|
|
+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',
|
|
|
@@ -487,18 +761,20 @@ function createExpected(area, address, byteLength, isWrite, kind) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function createInfoExpected(kind = 'storage-info-read') {
|
|
|
+function createControlExpected(operation, kind = 'storage-control', options = {}) {
|
|
|
+ const op = toByte(Number(operation), '特殊指令')
|
|
|
+
|
|
|
return {
|
|
|
- address: 0,
|
|
|
- area: AREA.INFO,
|
|
|
- byteLength: INFO_DATA_BYTE_LENGTH,
|
|
|
- command: buildCommand(AREA.INFO, false),
|
|
|
- frame: buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
|
|
|
- isInfo: true,
|
|
|
+ area: CMD_CONTROL,
|
|
|
+ byteLength: 0,
|
|
|
+ command: CMD_CONTROL,
|
|
|
+ expectedByteLength: Number(options.expectedByteLength) || 0,
|
|
|
+ isControl: true,
|
|
|
isWrite: false,
|
|
|
kind,
|
|
|
- operation: 'read',
|
|
|
- protocol: PROTOCOL_NAME
|
|
|
+ operation: op,
|
|
|
+ protocol: PROTOCOL_NAME,
|
|
|
+ responseBufferHint: CONTROL_RESPONSE_HEADER_LENGTH + (Number(options.expectedByteLength) || 0) + 2
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -532,14 +808,14 @@ function splitQuantity(startAddress, quantity, maxQuantity) {
|
|
|
}
|
|
|
|
|
|
function getReadChunks(startAddress, byteLength, options = {}) {
|
|
|
- const maxByteLength = getMaxReadByteLength(options.maxFrameBytes)
|
|
|
+ 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)
|
|
|
+ const maxByteLength = getMaxWriteByteLength(options.maxFrameBytes, options.area)
|
|
|
const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length)
|
|
|
let offset = 0
|
|
|
|
|
|
@@ -554,119 +830,9 @@ function getWriteChunks(startAddress, bytes, options = {}) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-function sendReadChunk(area, chunk, label, kind, options = {}) {
|
|
|
- const normalizedArea = normalizeMemoryArea(area)
|
|
|
-
|
|
|
- return transport.sendManagedFrame(
|
|
|
- buildReadFrame(normalizedArea, chunk.address, chunk.quantity, {
|
|
|
- maxFrameBytes: options.maxFrameBytes
|
|
|
- }),
|
|
|
- label,
|
|
|
- createExpected(normalizedArea, chunk.address, chunk.quantity, false, kind),
|
|
|
- {
|
|
|
- maxFrameBytes: options.maxFrameBytes,
|
|
|
- showModal: options.showModal
|
|
|
- }
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-async function readMemory(area, startAddress, byteLength, label, kind = 'storage-memory-read', options = {}) {
|
|
|
- const normalizedArea = normalizeMemoryArea(area)
|
|
|
- const bytes = []
|
|
|
- const chunks = getReadChunks(startAddress, byteLength, options)
|
|
|
-
|
|
|
- for (const chunk of chunks) {
|
|
|
- const response = await sendReadChunk(
|
|
|
- normalizedArea,
|
|
|
- chunk,
|
|
|
- getChunkLabel(label, chunks, chunk),
|
|
|
- kind,
|
|
|
- options
|
|
|
- )
|
|
|
- if (!response) return null
|
|
|
-
|
|
|
- const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : []
|
|
|
- dataBytes.forEach((byte, index) => {
|
|
|
- bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF
|
|
|
- })
|
|
|
-
|
|
|
- if (typeof options.onChunk === 'function') {
|
|
|
- options.onChunk(response, chunk)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return bytes
|
|
|
-}
|
|
|
-
|
|
|
-async function writeMemory(area, startAddress, bytes, label, kind = 'storage-memory-write', options = {}) {
|
|
|
- const normalizedArea = normalizeMemoryArea(area)
|
|
|
- const chunks = getWriteChunks(startAddress, bytes, options)
|
|
|
-
|
|
|
- for (const chunk of chunks) {
|
|
|
- const response = await transport.sendManagedFrame(
|
|
|
- buildWriteFrame(normalizedArea, chunk.address, chunk.dataBytes, {
|
|
|
- maxFrameBytes: options.maxFrameBytes
|
|
|
- }),
|
|
|
- getChunkLabel(label, chunks, chunk),
|
|
|
- createExpected(normalizedArea, chunk.address, chunk.quantity, true, kind),
|
|
|
- {
|
|
|
- maxFrameBytes: options.maxFrameBytes,
|
|
|
- showModal: options.showModal
|
|
|
- }
|
|
|
- )
|
|
|
- if (!response) return false
|
|
|
-
|
|
|
- if (typeof options.onChunk === 'function') {
|
|
|
- options.onChunk(response, chunk)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return true
|
|
|
-}
|
|
|
-
|
|
|
-async function readCodeInfoBlock(label = '同步info', kind = 'storage-info-read', options = {}) {
|
|
|
- const infoResponse = await transport.sendManagedFrame(
|
|
|
- buildInfoFrame(0, INFO_DATA_BYTE_LENGTH),
|
|
|
- label,
|
|
|
- createInfoExpected(`${kind}-info`),
|
|
|
- {
|
|
|
- maxFrameBytes: options.maxFrameBytes,
|
|
|
- showModal: options.showModal
|
|
|
- }
|
|
|
- )
|
|
|
- if (!infoResponse) return null
|
|
|
-
|
|
|
- const codeInfoAddress = Number(infoResponse.codeInfoAddress || 0)
|
|
|
- const codeInfoByteLength = Number(infoResponse.codeInfoByteLength || 0)
|
|
|
- if (!codeInfoByteLength || codeInfoByteLength > 0xFFFF) {
|
|
|
- transport.showCommandAlert(label, 'info 信息块长度无效')
|
|
|
- return null
|
|
|
- }
|
|
|
-
|
|
|
- const bytes = await readMemory(
|
|
|
- AREA.CODE,
|
|
|
- codeInfoAddress,
|
|
|
- codeInfoByteLength,
|
|
|
- label,
|
|
|
- kind,
|
|
|
- options
|
|
|
- )
|
|
|
- if (!bytes) return null
|
|
|
-
|
|
|
- return {
|
|
|
- codeInfoAddress,
|
|
|
- codeInfoByteLength,
|
|
|
- codeInfoBytes: bytes,
|
|
|
- infoBytes: Array.isArray(infoResponse.infoBytes)
|
|
|
- ? infoResponse.infoBytes
|
|
|
- : [],
|
|
|
- codeInfoMemoryType: AREA.CODE
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
const response = {
|
|
|
+ createControlExpected,
|
|
|
createExpected,
|
|
|
- createInfoExpected,
|
|
|
formatExceptionMessage,
|
|
|
getExceptionText,
|
|
|
getExpectedResponseLength,
|
|
|
@@ -677,57 +843,68 @@ const response = {
|
|
|
readResponseFromBuffer
|
|
|
}
|
|
|
|
|
|
-const client = {
|
|
|
- AREA,
|
|
|
- getMaxReadByteLength,
|
|
|
- getMaxWriteByteLength,
|
|
|
- getReadChunks,
|
|
|
- getWriteChunks,
|
|
|
- readCodeInfoBlock,
|
|
|
- readMemory,
|
|
|
- splitQuantity,
|
|
|
- writeMemory
|
|
|
-}
|
|
|
-
|
|
|
module.exports = {
|
|
|
AREA,
|
|
|
AREA_BY_NAME,
|
|
|
AREA_NAMES,
|
|
|
- CMD_AREA_MASK,
|
|
|
+ ADDRESS16_BYTE_LENGTH,
|
|
|
+ ADDRESS32_BYTE_LENGTH,
|
|
|
+ CMD_CONTROL,
|
|
|
+ CMD_CONTROL_FLAG,
|
|
|
+ CMD_ADDRESS_MODE_MASK,
|
|
|
CMD_ERR_MASK,
|
|
|
- CMD_INFO,
|
|
|
+ CMD_RESERVED_MASK,
|
|
|
CMD_WRITE_MASK,
|
|
|
+ CONTROL_OP,
|
|
|
+ CONTROL_RESPONSE_HEADER_LENGTH,
|
|
|
+ CONTROL_STATUS_MESSAGES,
|
|
|
DEFAULT_MAX_FRAME_BYTES,
|
|
|
EXCEPTION_MESSAGES,
|
|
|
EXCEPTION_RESPONSE_LENGTH,
|
|
|
- INFO_REQUEST_LENGTH,
|
|
|
- INFO_RESPONSE_LENGTH,
|
|
|
- INFO_DATA_BYTE_LENGTH,
|
|
|
+ CODE_INFO_DESCRIPTOR_BYTE_LENGTH,
|
|
|
MAX_PAYLOAD_BYTES,
|
|
|
+ MEMORY_ENDIAN,
|
|
|
+ MEMORY_ENDIAN_MARK_BIG,
|
|
|
+ MEMORY_ENDIAN_MARK_LITTLE,
|
|
|
PROTOCOL_NAME,
|
|
|
- READ_REQUEST_LENGTH,
|
|
|
- READ_RESPONSE_OVERHEAD,
|
|
|
+ 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,
|
|
|
- WRITE_RESPONSE_LENGTH,
|
|
|
+ WRITE_REQUEST_OVERHEAD_16,
|
|
|
+ WRITE_REQUEST_OVERHEAD_32,
|
|
|
+ WRITE_RESPONSE_LENGTH_16,
|
|
|
+ WRITE_RESPONSE_LENGTH_32,
|
|
|
appendStorageCrc,
|
|
|
buildCommand,
|
|
|
- buildInfoFrame,
|
|
|
+ buildCodeInfoDescriptorFrame,
|
|
|
+ buildControlReferenceFrame,
|
|
|
+ buildControlFrame,
|
|
|
buildReadFrame,
|
|
|
buildWriteFrame,
|
|
|
- client,
|
|
|
+ createControlExpected,
|
|
|
+ createExpected,
|
|
|
decodeCommand,
|
|
|
formatHex,
|
|
|
+ getChunkLabel,
|
|
|
+ getReadChunks,
|
|
|
+ getWriteChunks,
|
|
|
getMaxReadByteLength,
|
|
|
getMaxWriteByteLength,
|
|
|
hasValidStorageCrc,
|
|
|
+ normalizeDescriptorAddressWidth,
|
|
|
normalizeArea,
|
|
|
normalizeMaxFrameBytes,
|
|
|
normalizeMemoryArea,
|
|
|
+ resolveDescriptorMaxFrameBytes,
|
|
|
response,
|
|
|
+ splitDword,
|
|
|
+ splitQuantity,
|
|
|
splitWord,
|
|
|
toByte,
|
|
|
toByteLength,
|
|
|
+ toUint32,
|
|
|
toWord
|
|
|
}
|