|
|
@@ -0,0 +1,657 @@
|
|
|
+const {
|
|
|
+ bytesToBase64,
|
|
|
+ toByteArray
|
|
|
+} = require('./binary-utils')
|
|
|
+const {
|
|
|
+ clampInteger
|
|
|
+} = require('./base-utils')
|
|
|
+
|
|
|
+const CRC16_MODBUS_TABLE = [
|
|
|
+ 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
|
|
+ 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
|
|
+ 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
|
|
+ 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
|
|
+ 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
|
|
+ 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
|
|
+ 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
|
|
+ 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
|
|
+ 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
|
|
+ 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
|
|
+ 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
|
|
+ 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
|
|
+ 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
|
|
+ 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
|
|
+ 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
|
|
+ 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
|
|
+ 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
|
|
+ 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
|
|
+ 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
|
|
+ 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
|
|
+ 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
|
|
+ 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
|
|
+ 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
|
|
+ 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
|
|
+ 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
|
|
+ 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
|
|
+ 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
|
|
+ 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
|
|
+ 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
|
|
+ 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
|
|
+ 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
|
|
+ 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
|
|
+]
|
|
|
+
|
|
|
+const CRC16_CCITT_INIT = 0xFFFF
|
|
|
+const CRC16_CCITT_POLY = 0x1021
|
|
|
+const BYTE_ORDER_HIGH = 'high'
|
|
|
+const BYTE_ORDER_LOW = 'low'
|
|
|
+const CRC_TABLE_CACHE = {}
|
|
|
+
|
|
|
+function createReflectByteTable() {
|
|
|
+ const table = []
|
|
|
+
|
|
|
+ for (let index = 0; index < 256; index += 1) {
|
|
|
+ let source = index
|
|
|
+ let reflected = 0
|
|
|
+
|
|
|
+ for (let bit = 0; bit < 8; bit += 1) {
|
|
|
+ reflected = (reflected << 1) | (source & 0x01)
|
|
|
+ source >>= 1
|
|
|
+ }
|
|
|
+
|
|
|
+ table[index] = reflected & 0xFF
|
|
|
+ }
|
|
|
+
|
|
|
+ return table
|
|
|
+}
|
|
|
+
|
|
|
+const REFLECT_BYTE_TABLE = createReflectByteTable()
|
|
|
+
|
|
|
+function getCrcNumberMask(width) {
|
|
|
+ if (width === 8) return 0xFF
|
|
|
+ if (width === 16) return 0xFFFF
|
|
|
+
|
|
|
+ return 0
|
|
|
+}
|
|
|
+
|
|
|
+function createMsbCrcTable(width, polynomial) {
|
|
|
+ const table = []
|
|
|
+ const mask = getCrcNumberMask(width)
|
|
|
+ const topBit = 1 << (width - 1)
|
|
|
+ const shift = width - 8
|
|
|
+ const poly = polynomial & mask
|
|
|
+
|
|
|
+ for (let index = 0; index < 256; index += 1) {
|
|
|
+ let crc = shift > 0 ? (index << shift) : index
|
|
|
+
|
|
|
+ for (let bit = 0; bit < 8; bit += 1) {
|
|
|
+ crc = (crc & topBit)
|
|
|
+ ? ((crc << 1) ^ poly)
|
|
|
+ : (crc << 1)
|
|
|
+ crc &= mask
|
|
|
+ }
|
|
|
+
|
|
|
+ table[index] = crc
|
|
|
+ }
|
|
|
+
|
|
|
+ return table
|
|
|
+}
|
|
|
+
|
|
|
+function getMsbCrcTable(width, polynomial) {
|
|
|
+ const mask = getCrcNumberMask(width)
|
|
|
+ if (!mask) return null
|
|
|
+
|
|
|
+ const key = `${width}:${polynomial & mask}`
|
|
|
+ if (!CRC_TABLE_CACHE[key]) {
|
|
|
+ CRC_TABLE_CACHE[key] = createMsbCrcTable(width, polynomial)
|
|
|
+ }
|
|
|
+
|
|
|
+ return CRC_TABLE_CACHE[key]
|
|
|
+}
|
|
|
+
|
|
|
+function reflectNumberBits(value, width) {
|
|
|
+ if (width === 8) return REFLECT_BYTE_TABLE[value & 0xFF]
|
|
|
+ if (width === 16) {
|
|
|
+ return ((REFLECT_BYTE_TABLE[value & 0xFF] << 8) | REFLECT_BYTE_TABLE[(value >> 8) & 0xFF]) & 0xFFFF
|
|
|
+ }
|
|
|
+
|
|
|
+ return Number(reflectBits(BigInt(value), width))
|
|
|
+}
|
|
|
+
|
|
|
+const CRC16_CCITT_TABLE = getMsbCrcTable(16, CRC16_CCITT_POLY)
|
|
|
+
|
|
|
+function hasOwnOption(options, key) {
|
|
|
+ return Object.prototype.hasOwnProperty.call(options, key)
|
|
|
+}
|
|
|
+
|
|
|
+function getSourceOptions(options) {
|
|
|
+ return typeof options === 'number'
|
|
|
+ ? { initialValue: options }
|
|
|
+ : (options || {})
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeChecksumOptions(options = {}, defaultByteOrder = BYTE_ORDER_HIGH, requireByteOrder = false) {
|
|
|
+ if (typeof options === 'number') {
|
|
|
+ if (requireByteOrder) {
|
|
|
+ throw new Error("16位校验需要指定 byteOrder: 'high' 或 'low'")
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ byteOrder: defaultByteOrder,
|
|
|
+ initialValue: options
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const source = options || {}
|
|
|
+ const hasByteOrder = hasOwnOption(source, 'byteOrder') || hasOwnOption(source, 'lowByteFirst')
|
|
|
+ if (requireByteOrder && !hasByteOrder) {
|
|
|
+ throw new Error("16位校验需要指定 byteOrder: 'high' 或 'low'")
|
|
|
+ }
|
|
|
+
|
|
|
+ let byteOrder = source.byteOrder || defaultByteOrder
|
|
|
+
|
|
|
+ if (source.lowByteFirst === true || byteOrder === 'low' || byteOrder === 'little' || byteOrder === 'le') {
|
|
|
+ byteOrder = BYTE_ORDER_LOW
|
|
|
+ } else {
|
|
|
+ byteOrder = BYTE_ORDER_HIGH
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...source,
|
|
|
+ byteOrder
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getOptionNumber(options, key, fallback, mask) {
|
|
|
+ const value = hasOwnOption(options, key) ? Number(options[key]) : fallback
|
|
|
+ if (!Number.isFinite(value)) return fallback & mask
|
|
|
+
|
|
|
+ return value & mask
|
|
|
+}
|
|
|
+
|
|
|
+function appendChecksum8Value(bytes, checksum) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+
|
|
|
+ return frame.concat([checksum & 0xFF])
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidChecksum8By(bytes, checksumFunction, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 2) return false
|
|
|
+
|
|
|
+ const expected = checksumFunction(frame.slice(0, -1), options) & 0xFF
|
|
|
+ const received = frame[frame.length - 1] & 0xFF
|
|
|
+
|
|
|
+ return expected === received
|
|
|
+}
|
|
|
+
|
|
|
+function appendChecksum16(bytes, checksum, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const highByte = (checksum >> 8) & 0xFF
|
|
|
+ const lowByte = checksum & 0xFF
|
|
|
+
|
|
|
+ return normalized.byteOrder === BYTE_ORDER_LOW
|
|
|
+ ? frame.concat([lowByte, highByte])
|
|
|
+ : frame.concat([highByte, lowByte])
|
|
|
+}
|
|
|
+
|
|
|
+function readChecksum16(bytes, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
|
|
|
+ const firstByte = frame[frame.length - 2] & 0xFF
|
|
|
+ const secondByte = frame[frame.length - 1] & 0xFF
|
|
|
+
|
|
|
+ return normalized.byteOrder === BYTE_ORDER_LOW
|
|
|
+ ? (firstByte | (secondByte << 8))
|
|
|
+ : ((firstByte << 8) | secondByte)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidChecksum16(bytes, checksumFunction, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 2) return false
|
|
|
+
|
|
|
+ const expected = checksumFunction(frame.slice(0, -2), options) & 0xFFFF
|
|
|
+ const received = readChecksum16(frame, options)
|
|
|
+
|
|
|
+ return expected === received
|
|
|
+}
|
|
|
+
|
|
|
+function checksum8(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
|
|
|
+ let checksum = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ checksum = (checksum + (byte & 0xFF)) & 0xFF
|
|
|
+ })
|
|
|
+
|
|
|
+ return (checksum ^ finalXor) & 0xFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendChecksum8(bytes, options = {}) {
|
|
|
+ return appendChecksum8Value(bytes, checksum8(bytes, options))
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidChecksum8Value(bytes, options = {}) {
|
|
|
+ return hasValidChecksum8By(bytes, checksum8, options)
|
|
|
+}
|
|
|
+
|
|
|
+function xor8(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
|
|
|
+ let checksum = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ checksum = (checksum ^ (byte & 0xFF)) & 0xFF
|
|
|
+ })
|
|
|
+
|
|
|
+ return (checksum ^ finalXor) & 0xFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendXor8(bytes, options = {}) {
|
|
|
+ return appendChecksum8Value(bytes, xor8(bytes, options))
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidXor8(bytes, options = {}) {
|
|
|
+ return hasValidChecksum8By(bytes, xor8, options)
|
|
|
+}
|
|
|
+
|
|
|
+function lrc8(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const sum = checksum8(bytes, {
|
|
|
+ ...normalized,
|
|
|
+ finalXor: 0x00
|
|
|
+ })
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
|
|
|
+
|
|
|
+ return (((-sum) & 0xFF) ^ finalXor) & 0xFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendLrc8(bytes, options = {}) {
|
|
|
+ return appendChecksum8Value(bytes, lrc8(bytes, options))
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidLrc8(bytes, options = {}) {
|
|
|
+ return hasValidChecksum8By(bytes, lrc8, options)
|
|
|
+}
|
|
|
+
|
|
|
+function crc8(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const polynomial = getOptionNumber(normalized, 'polynomial', 0x07, 0xFF)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
|
|
|
+ const table = getMsbCrcTable(8, polynomial)
|
|
|
+ let crc = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ crc = table[(crc ^ (byte & 0xFF)) & 0xFF]
|
|
|
+ })
|
|
|
+
|
|
|
+ return (crc ^ finalXor) & 0xFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendCrc8(bytes, options = {}) {
|
|
|
+ return appendChecksum8Value(bytes, crc8(bytes, options))
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidCrc8(bytes, options = {}) {
|
|
|
+ return hasValidChecksum8By(bytes, crc8, options)
|
|
|
+}
|
|
|
+
|
|
|
+function checksum16(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', 0x0000, 0xFFFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
|
|
|
+ let checksum = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ checksum = (checksum + (byte & 0xFF)) & 0xFFFF
|
|
|
+ })
|
|
|
+
|
|
|
+ return (checksum ^ finalXor) & 0xFFFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendChecksum16Value(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
|
|
|
+
|
|
|
+ return appendChecksum16(bytes, checksum16(bytes, normalized), normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidChecksum16Value(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
|
|
|
+
|
|
|
+ return hasValidChecksum16(bytes, checksum16, normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function crc16Ibm(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', 0x0000, 0xFFFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
|
|
|
+ let crc = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ crc = (crc >> 8) ^ CRC16_MODBUS_TABLE[(crc ^ byte) & 0xFF]
|
|
|
+ })
|
|
|
+
|
|
|
+ return (crc ^ finalXor) & 0xFFFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendCrc16Ibm(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW, true)
|
|
|
+
|
|
|
+ return appendChecksum16(bytes, crc16Ibm(bytes, normalized), normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidCrc16Ibm(bytes, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 4) return false
|
|
|
+
|
|
|
+ return hasValidChecksum16(frame, crc16Ibm, normalizeChecksumOptions(options, BYTE_ORDER_LOW, true))
|
|
|
+}
|
|
|
+
|
|
|
+function crc16Modbus(bytes, options = {}) {
|
|
|
+ const source = getSourceOptions(options)
|
|
|
+ const normalized = {
|
|
|
+ ...normalizeChecksumOptions(options, BYTE_ORDER_LOW),
|
|
|
+ initialValue: hasOwnOption(source, 'initialValue') ? source.initialValue : 0xFFFF
|
|
|
+ }
|
|
|
+
|
|
|
+ return crc16Ibm(bytes, normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function appendCrc16Modbus(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW, true)
|
|
|
+
|
|
|
+ return appendChecksum16(bytes, crc16Modbus(bytes, normalized), normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidCrc16Modbus(bytes, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 4) return false
|
|
|
+
|
|
|
+ return hasValidChecksum16(frame, crc16Modbus, normalizeChecksumOptions(options, BYTE_ORDER_LOW, true))
|
|
|
+}
|
|
|
+
|
|
|
+function crc16Ccitt(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH)
|
|
|
+ const initialValue = getOptionNumber(normalized, 'initialValue', CRC16_CCITT_INIT, 0xFFFF)
|
|
|
+ const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
|
|
|
+ let crc = initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((byte) => {
|
|
|
+ crc = ((crc << 8) ^ CRC16_CCITT_TABLE[((crc >> 8) ^ byte) & 0xFF]) & 0xFFFF
|
|
|
+ })
|
|
|
+
|
|
|
+ return (crc ^ finalXor) & 0xFFFF
|
|
|
+}
|
|
|
+
|
|
|
+function appendCrc16Ccitt(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
|
|
|
+
|
|
|
+ return appendChecksum16(bytes, crc16Ccitt(bytes, normalized), normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidCrc16Ccitt(bytes, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 4) return false
|
|
|
+
|
|
|
+ return hasValidChecksum16(frame, crc16Ccitt, normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true))
|
|
|
+}
|
|
|
+
|
|
|
+function crc16Xmodem(bytes, options = {}) {
|
|
|
+ const source = getSourceOptions(options)
|
|
|
+ const normalized = {
|
|
|
+ ...normalizeChecksumOptions(source, BYTE_ORDER_HIGH),
|
|
|
+ initialValue: hasOwnOption(source, 'initialValue') ? source.initialValue : 0x0000
|
|
|
+ }
|
|
|
+
|
|
|
+ return crc16Ccitt(bytes, normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function appendCrc16Xmodem(bytes, options = {}) {
|
|
|
+ const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
|
|
|
+
|
|
|
+ return appendChecksum16(bytes, crc16Xmodem(bytes, normalized), normalized)
|
|
|
+}
|
|
|
+
|
|
|
+function hasValidCrc16Xmodem(bytes, options = {}) {
|
|
|
+ const frame = toByteArray(bytes)
|
|
|
+ if (frame.length < 4) return false
|
|
|
+
|
|
|
+ return hasValidChecksum16(frame, crc16Xmodem, normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true))
|
|
|
+}
|
|
|
+
|
|
|
+const CRC_ALGORITHM_PRESETS = [
|
|
|
+ { key: 'crc-8', label: 'CRC-8', width: 8, poly: '07', init: '00', xorOut: '00', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-8-itu', label: 'CRC-8-ITU', width: 8, poly: '07', init: '00', xorOut: '55', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-8-rohc', label: 'CRC-8-ROHC', width: 8, poly: '07', init: 'FF', xorOut: '00', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-8-maxim', label: 'CRC-8-MAXIM', width: 8, poly: '31', init: '00', xorOut: '00', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-ibm', label: 'CRC-16-IBM', width: 16, poly: '8005', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-usb', label: 'CRC-16-USB', width: 16, poly: '8005', init: 'FFFF', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-maxim', label: 'CRC-16-MAXIM', width: 16, poly: '8005', init: '0000', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-ccitt', label: 'CRC-16-CCITT', width: 16, poly: '1021', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-ccitt-false', label: 'CRC-16-CCITT-FALSE', width: 16, poly: '1021', init: 'FFFF', xorOut: '0000', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-16-x25', label: 'CRC-16-X25', width: 16, poly: '1021', init: 'FFFF', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-xmodem', label: 'CRC-16-XMODEM', width: 16, poly: '1021', init: '0000', xorOut: '0000', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-16-xmodem2', label: 'CRC-16-XMODEM2', width: 16, poly: '8408', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-16-dnp', label: 'CRC-16-DNP', width: 16, poly: '3D65', init: '0000', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-24-q', label: 'CRC-24-Q', width: 24, poly: '864CFB', init: '000000', xorOut: '000000', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-32', label: 'CRC-32', width: 32, poly: '04C11DB7', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-32-c', label: 'CRC-32-C', width: 32, poly: '1EDC6F41', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-32-koopman', label: 'CRC-32-KOOPMAN', width: 32, poly: '741B8CD7', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-32-mpeg-2', label: 'CRC-32-MPEG-2', width: 32, poly: '04C11DB7', init: 'FFFFFFFF', xorOut: '00000000', reflectIn: false, reflectOut: false },
|
|
|
+ { key: 'crc-64-iso', label: 'CRC-64-ISO', width: 64, poly: '000000000000001B', init: 'FFFFFFFFFFFFFFFF', xorOut: 'FFFFFFFFFFFFFFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'crc-64-ecma', label: 'CRC-64-ECMA', width: 64, poly: '42F0E1EBA9EA3693', init: 'FFFFFFFFFFFFFFFF', xorOut: 'FFFFFFFFFFFFFFFF', reflectIn: true, reflectOut: true },
|
|
|
+ { key: 'custom', label: '自定义', width: 16, poly: '1021', init: 'FFFF', xorOut: '0000', reflectIn: false, reflectOut: false, custom: true }
|
|
|
+]
|
|
|
+
|
|
|
+function maskForWidth(width) {
|
|
|
+ const bitWidth = Number(width)
|
|
|
+ if (!Number.isInteger(bitWidth) || bitWidth < 1 || bitWidth > 64) {
|
|
|
+ throw new Error('CRC 位宽需为 1 - 64')
|
|
|
+ }
|
|
|
+
|
|
|
+ return (1n << BigInt(bitWidth)) - 1n
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeHexText(value, fallback = '0') {
|
|
|
+ const text = String(value === undefined || value === null ? '' : value).trim()
|
|
|
+ if (!text) return fallback
|
|
|
+
|
|
|
+ return text.toUpperCase().startsWith('0X') ? text.slice(2) : text
|
|
|
+}
|
|
|
+
|
|
|
+function parseHexBigInt(value, label, fallback = '0') {
|
|
|
+ if (typeof value === 'bigint') return value
|
|
|
+ if (typeof value === 'number' && Number.isFinite(value)) return BigInt(Math.max(0, Math.trunc(value)))
|
|
|
+
|
|
|
+ const hexText = normalizeHexText(value, fallback)
|
|
|
+ if (!/^[0-9A-F]+$/i.test(hexText)) {
|
|
|
+ throw new Error(`${label}需为十六进制`)
|
|
|
+ }
|
|
|
+
|
|
|
+ return BigInt(`0x${hexText}`)
|
|
|
+}
|
|
|
+
|
|
|
+function reflectBits(value, width) {
|
|
|
+ let source = BigInt(value)
|
|
|
+ let reflected = 0n
|
|
|
+
|
|
|
+ for (let index = 0; index < width; index += 1) {
|
|
|
+ reflected = (reflected << 1n) | (source & 1n)
|
|
|
+ source >>= 1n
|
|
|
+ }
|
|
|
+
|
|
|
+ return reflected
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeCrcConfig(config = {}) {
|
|
|
+ const width = clampInteger(config.width, 1, 64, 16)
|
|
|
+ const mask = maskForWidth(width)
|
|
|
+ const polyValue = config.poly !== undefined ? config.poly : config.polynomial
|
|
|
+ const initValue = config.init !== undefined ? config.init : config.initialValue
|
|
|
+ const xorValue = config.xorOut !== undefined ? config.xorOut : config.finalXor
|
|
|
+ const presetKey = String(config.key || config.presetKey || '')
|
|
|
+
|
|
|
+ return {
|
|
|
+ finalXor: parseHexBigInt(xorValue, '结果异或值', '0') & mask,
|
|
|
+ initialValue: parseHexBigInt(initValue, '初始值', '0') & mask,
|
|
|
+ mask,
|
|
|
+ presetKey,
|
|
|
+ polynomial: parseHexBigInt(polyValue, 'Poly', '0') & mask,
|
|
|
+ reflectIn: !!(config.reflectIn || config.refin),
|
|
|
+ reflectOut: !!(config.reflectOut || config.refout),
|
|
|
+ useLookupTable: config.useLookupTable === true || (
|
|
|
+ !!presetKey && presetKey !== 'custom' && (width === 8 || width === 16)
|
|
|
+ ),
|
|
|
+ width
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function computeCrcTable(bytes, normalized) {
|
|
|
+ if (!normalized.useLookupTable) return null
|
|
|
+
|
|
|
+ const width = normalized.width
|
|
|
+ const mask = getCrcNumberMask(width)
|
|
|
+ if (!mask) return null
|
|
|
+
|
|
|
+ const table = getMsbCrcTable(width, Number(normalized.polynomial & BigInt(mask)))
|
|
|
+ const shift = width - 8
|
|
|
+ let crc = Number(normalized.initialValue & BigInt(mask))
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((sourceByte) => {
|
|
|
+ const byte = normalized.reflectIn
|
|
|
+ ? REFLECT_BYTE_TABLE[sourceByte & 0xFF]
|
|
|
+ : (sourceByte & 0xFF)
|
|
|
+ const tableIndex = shift > 0
|
|
|
+ ? (((crc >> shift) ^ byte) & 0xFF)
|
|
|
+ : ((crc ^ byte) & 0xFF)
|
|
|
+
|
|
|
+ crc = shift > 0
|
|
|
+ ? (((crc << 8) & mask) ^ table[tableIndex])
|
|
|
+ : table[tableIndex]
|
|
|
+ crc &= mask
|
|
|
+ })
|
|
|
+
|
|
|
+ if (normalized.reflectOut) {
|
|
|
+ crc = reflectNumberBits(crc, width) & mask
|
|
|
+ }
|
|
|
+
|
|
|
+ return BigInt((crc ^ Number(normalized.finalXor & BigInt(mask))) & mask)
|
|
|
+}
|
|
|
+
|
|
|
+function computeCrc(bytes, config = {}) {
|
|
|
+ const normalized = normalizeCrcConfig(config)
|
|
|
+ const tableValue = computeCrcTable(bytes, normalized)
|
|
|
+ if (tableValue !== null) return tableValue
|
|
|
+
|
|
|
+ const topBit = 1n << BigInt(normalized.width - 1)
|
|
|
+ let crc = normalized.initialValue
|
|
|
+
|
|
|
+ toByteArray(bytes).forEach((sourceByte) => {
|
|
|
+ const byte = normalized.reflectIn
|
|
|
+ ? Number(reflectBits(BigInt(sourceByte & 0xFF), 8))
|
|
|
+ : (sourceByte & 0xFF)
|
|
|
+
|
|
|
+ for (let bitMask = 0x80; bitMask > 0; bitMask >>= 1) {
|
|
|
+ let bitSet = (crc & topBit) !== 0n
|
|
|
+ crc = (crc << 1n) & normalized.mask
|
|
|
+
|
|
|
+ if (byte & bitMask) {
|
|
|
+ bitSet = !bitSet
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bitSet) {
|
|
|
+ crc = (crc ^ normalized.polynomial) & normalized.mask
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (normalized.reflectOut) {
|
|
|
+ crc = reflectBits(crc, normalized.width) & normalized.mask
|
|
|
+ }
|
|
|
+
|
|
|
+ return (crc ^ normalized.finalXor) & normalized.mask
|
|
|
+}
|
|
|
+
|
|
|
+function formatCrcHex(value, width) {
|
|
|
+ const hexLength = Math.max(1, Math.ceil(Number(width || 1) / 4))
|
|
|
+
|
|
|
+ return `0x${BigInt(value).toString(16).toUpperCase().padStart(hexLength, '0')}`
|
|
|
+}
|
|
|
+
|
|
|
+function formatCrcBin(value, width) {
|
|
|
+ return BigInt(value).toString(2).padStart(Number(width || 1), '0')
|
|
|
+}
|
|
|
+
|
|
|
+function crcValueToBytes(value, width) {
|
|
|
+ const byteLength = Math.max(1, Math.ceil(Number(width || 1) / 8))
|
|
|
+ const result = []
|
|
|
+ let source = BigInt(value)
|
|
|
+
|
|
|
+ for (let index = byteLength - 1; index >= 0; index -= 1) {
|
|
|
+ result[index] = Number(source & 0xFFn)
|
|
|
+ source >>= 8n
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+function calculateCrc(bytes, config = {}) {
|
|
|
+ const normalized = normalizeCrcConfig(config)
|
|
|
+ const value = computeCrc(bytes, normalized)
|
|
|
+ const resultBytes = crcValueToBytes(value, normalized.width)
|
|
|
+
|
|
|
+ return {
|
|
|
+ base64: bytesToBase64(resultBytes),
|
|
|
+ bin: formatCrcBin(value, normalized.width),
|
|
|
+ bytes: resultBytes,
|
|
|
+ hex: formatCrcHex(value, normalized.width),
|
|
|
+ value,
|
|
|
+ width: normalized.width
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ BYTE_ORDER_HIGH,
|
|
|
+ BYTE_ORDER_LOW,
|
|
|
+ CRC_ALGORITHM_PRESETS,
|
|
|
+ appendChecksum16: appendChecksum16Value,
|
|
|
+ appendChecksum8,
|
|
|
+ appendCrc16Ccitt,
|
|
|
+ appendCrc16Ibm,
|
|
|
+ appendCrc16Modbus,
|
|
|
+ appendCrc16Xmodem,
|
|
|
+ appendCrc8,
|
|
|
+ appendLrc8,
|
|
|
+ appendXor8,
|
|
|
+ bytesToBase64,
|
|
|
+ calculateCrc,
|
|
|
+ checksum16,
|
|
|
+ checksum8,
|
|
|
+ computeCrc,
|
|
|
+ crc16Ccitt,
|
|
|
+ crc16Ibm,
|
|
|
+ crc16Modbus,
|
|
|
+ crc16Xmodem,
|
|
|
+ crc8,
|
|
|
+ crcValueToBytes,
|
|
|
+ formatCrcBin,
|
|
|
+ formatCrcHex,
|
|
|
+ hasValidChecksum16: hasValidChecksum16Value,
|
|
|
+ hasValidChecksum8: hasValidChecksum8Value,
|
|
|
+ hasValidCrc16Ccitt,
|
|
|
+ hasValidCrc16Ibm,
|
|
|
+ hasValidCrc16Modbus,
|
|
|
+ hasValidCrc16Xmodem,
|
|
|
+ hasValidCrc8,
|
|
|
+ hasValidLrc8,
|
|
|
+ hasValidXor8,
|
|
|
+ lrc8,
|
|
|
+ normalizeCrcConfig,
|
|
|
+ readChecksum16,
|
|
|
+ xor8
|
|
|
+}
|