crc.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. const {
  2. bytesToBase64,
  3. toByteArray
  4. } = require('./binary-utils.js')
  5. const {
  6. clampInteger
  7. } = require('./base-utils.js')
  8. const CRC16_MODBUS_TABLE = [
  9. 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
  10. 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
  11. 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
  12. 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
  13. 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
  14. 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
  15. 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
  16. 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
  17. 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
  18. 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
  19. 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
  20. 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
  21. 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
  22. 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
  23. 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
  24. 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
  25. 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
  26. 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
  27. 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
  28. 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
  29. 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
  30. 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
  31. 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
  32. 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
  33. 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
  34. 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
  35. 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
  36. 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
  37. 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
  38. 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
  39. 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
  40. 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
  41. ]
  42. const CRC16_CCITT_INIT = 0xFFFF
  43. const CRC16_CCITT_POLY = 0x1021
  44. const BYTE_ORDER_HIGH = 'high'
  45. const BYTE_ORDER_LOW = 'low'
  46. const CRC_TABLE_CACHE = {}
  47. function createReflectByteTable() {
  48. const table = []
  49. for (let index = 0; index < 256; index += 1) {
  50. let source = index
  51. let reflected = 0
  52. for (let bit = 0; bit < 8; bit += 1) {
  53. reflected = (reflected << 1) | (source & 0x01)
  54. source >>= 1
  55. }
  56. table[index] = reflected & 0xFF
  57. }
  58. return table
  59. }
  60. const REFLECT_BYTE_TABLE = createReflectByteTable()
  61. function getCrcNumberMask(width) {
  62. if (width === 8) return 0xFF
  63. if (width === 16) return 0xFFFF
  64. return 0
  65. }
  66. function createMsbCrcTable(width, polynomial) {
  67. const table = []
  68. const mask = getCrcNumberMask(width)
  69. const topBit = 1 << (width - 1)
  70. const shift = width - 8
  71. const poly = polynomial & mask
  72. for (let index = 0; index < 256; index += 1) {
  73. let crc = shift > 0 ? (index << shift) : index
  74. for (let bit = 0; bit < 8; bit += 1) {
  75. crc = (crc & topBit)
  76. ? ((crc << 1) ^ poly)
  77. : (crc << 1)
  78. crc &= mask
  79. }
  80. table[index] = crc
  81. }
  82. return table
  83. }
  84. function getMsbCrcTable(width, polynomial) {
  85. const mask = getCrcNumberMask(width)
  86. if (!mask) return null
  87. const key = `${width}:${polynomial & mask}`
  88. if (!CRC_TABLE_CACHE[key]) {
  89. CRC_TABLE_CACHE[key] = createMsbCrcTable(width, polynomial)
  90. }
  91. return CRC_TABLE_CACHE[key]
  92. }
  93. function reflectNumberBits(value, width) {
  94. if (width === 8) return REFLECT_BYTE_TABLE[value & 0xFF]
  95. if (width === 16) {
  96. return ((REFLECT_BYTE_TABLE[value & 0xFF] << 8) | REFLECT_BYTE_TABLE[(value >> 8) & 0xFF]) & 0xFFFF
  97. }
  98. return Number(reflectBits(BigInt(value), width))
  99. }
  100. const CRC16_CCITT_TABLE = getMsbCrcTable(16, CRC16_CCITT_POLY)
  101. function hasOwnOption(options, key) {
  102. return Object.prototype.hasOwnProperty.call(options, key)
  103. }
  104. function getSourceOptions(options) {
  105. return typeof options === 'number'
  106. ? { initialValue: options }
  107. : (options || {})
  108. }
  109. function normalizeChecksumOptions(options = {}, defaultByteOrder = BYTE_ORDER_HIGH, requireByteOrder = false) {
  110. if (typeof options === 'number') {
  111. if (requireByteOrder) {
  112. throw new Error("16位校验需要指定 byteOrder: 'high' 或 'low'")
  113. }
  114. return {
  115. byteOrder: defaultByteOrder,
  116. initialValue: options
  117. }
  118. }
  119. const source = options || {}
  120. const hasByteOrder = hasOwnOption(source, 'byteOrder') || hasOwnOption(source, 'lowByteFirst')
  121. if (requireByteOrder && !hasByteOrder) {
  122. throw new Error("16位校验需要指定 byteOrder: 'high' 或 'low'")
  123. }
  124. let byteOrder = source.byteOrder || defaultByteOrder
  125. if (source.lowByteFirst === true || byteOrder === 'low' || byteOrder === 'little' || byteOrder === 'le') {
  126. byteOrder = BYTE_ORDER_LOW
  127. } else {
  128. byteOrder = BYTE_ORDER_HIGH
  129. }
  130. return {
  131. ...source,
  132. byteOrder
  133. }
  134. }
  135. function getOptionNumber(options, key, fallback, mask) {
  136. const value = hasOwnOption(options, key) ? Number(options[key]) : fallback
  137. if (!Number.isFinite(value)) return fallback & mask
  138. return value & mask
  139. }
  140. function appendChecksum8Value(bytes, checksum) {
  141. const frame = toByteArray(bytes)
  142. return frame.concat([checksum & 0xFF])
  143. }
  144. function hasValidChecksum8By(bytes, checksumFunction, options = {}) {
  145. const frame = toByteArray(bytes)
  146. if (frame.length < 2) return false
  147. const expected = checksumFunction(frame.slice(0, -1), options) & 0xFF
  148. const received = frame[frame.length - 1] & 0xFF
  149. return expected === received
  150. }
  151. function appendChecksum16(bytes, checksum, options = {}) {
  152. const frame = toByteArray(bytes)
  153. const normalized = normalizeChecksumOptions(options)
  154. const highByte = (checksum >> 8) & 0xFF
  155. const lowByte = checksum & 0xFF
  156. return normalized.byteOrder === BYTE_ORDER_LOW
  157. ? frame.concat([lowByte, highByte])
  158. : frame.concat([highByte, lowByte])
  159. }
  160. function readChecksum16(bytes, options = {}) {
  161. const frame = toByteArray(bytes)
  162. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
  163. const firstByte = frame[frame.length - 2] & 0xFF
  164. const secondByte = frame[frame.length - 1] & 0xFF
  165. return normalized.byteOrder === BYTE_ORDER_LOW
  166. ? (firstByte | (secondByte << 8))
  167. : ((firstByte << 8) | secondByte)
  168. }
  169. function hasValidChecksum16(bytes, checksumFunction, options = {}) {
  170. const frame = toByteArray(bytes)
  171. if (frame.length < 2) return false
  172. const expected = checksumFunction(frame.slice(0, -2), options) & 0xFFFF
  173. const received = readChecksum16(frame, options)
  174. return expected === received
  175. }
  176. function checksum8(bytes, options = {}) {
  177. const normalized = normalizeChecksumOptions(options)
  178. const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
  179. const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
  180. let checksum = initialValue
  181. toByteArray(bytes).forEach((byte) => {
  182. checksum = (checksum + (byte & 0xFF)) & 0xFF
  183. })
  184. return (checksum ^ finalXor) & 0xFF
  185. }
  186. function appendChecksum8(bytes, options = {}) {
  187. return appendChecksum8Value(bytes, checksum8(bytes, options))
  188. }
  189. function hasValidChecksum8Value(bytes, options = {}) {
  190. return hasValidChecksum8By(bytes, checksum8, options)
  191. }
  192. function xor8(bytes, options = {}) {
  193. const normalized = normalizeChecksumOptions(options)
  194. const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
  195. const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
  196. let checksum = initialValue
  197. toByteArray(bytes).forEach((byte) => {
  198. checksum = (checksum ^ (byte & 0xFF)) & 0xFF
  199. })
  200. return (checksum ^ finalXor) & 0xFF
  201. }
  202. function appendXor8(bytes, options = {}) {
  203. return appendChecksum8Value(bytes, xor8(bytes, options))
  204. }
  205. function hasValidXor8(bytes, options = {}) {
  206. return hasValidChecksum8By(bytes, xor8, options)
  207. }
  208. function lrc8(bytes, options = {}) {
  209. const normalized = normalizeChecksumOptions(options)
  210. const sum = checksum8(bytes, {
  211. ...normalized,
  212. finalXor: 0x00
  213. })
  214. const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
  215. return (((-sum) & 0xFF) ^ finalXor) & 0xFF
  216. }
  217. function appendLrc8(bytes, options = {}) {
  218. return appendChecksum8Value(bytes, lrc8(bytes, options))
  219. }
  220. function hasValidLrc8(bytes, options = {}) {
  221. return hasValidChecksum8By(bytes, lrc8, options)
  222. }
  223. function crc8(bytes, options = {}) {
  224. const normalized = normalizeChecksumOptions(options)
  225. const polynomial = getOptionNumber(normalized, 'polynomial', 0x07, 0xFF)
  226. const initialValue = getOptionNumber(normalized, 'initialValue', 0x00, 0xFF)
  227. const finalXor = getOptionNumber(normalized, 'finalXor', 0x00, 0xFF)
  228. const table = getMsbCrcTable(8, polynomial)
  229. let crc = initialValue
  230. toByteArray(bytes).forEach((byte) => {
  231. crc = table[(crc ^ (byte & 0xFF)) & 0xFF]
  232. })
  233. return (crc ^ finalXor) & 0xFF
  234. }
  235. function appendCrc8(bytes, options = {}) {
  236. return appendChecksum8Value(bytes, crc8(bytes, options))
  237. }
  238. function hasValidCrc8(bytes, options = {}) {
  239. return hasValidChecksum8By(bytes, crc8, options)
  240. }
  241. function checksum16(bytes, options = {}) {
  242. const normalized = normalizeChecksumOptions(options)
  243. const initialValue = getOptionNumber(normalized, 'initialValue', 0x0000, 0xFFFF)
  244. const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
  245. let checksum = initialValue
  246. toByteArray(bytes).forEach((byte) => {
  247. checksum = (checksum + (byte & 0xFF)) & 0xFFFF
  248. })
  249. return (checksum ^ finalXor) & 0xFFFF
  250. }
  251. function appendChecksum16Value(bytes, options = {}) {
  252. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
  253. return appendChecksum16(bytes, checksum16(bytes, normalized), normalized)
  254. }
  255. function hasValidChecksum16Value(bytes, options = {}) {
  256. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
  257. return hasValidChecksum16(bytes, checksum16, normalized)
  258. }
  259. function crc16Ibm(bytes, options = {}) {
  260. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW)
  261. const initialValue = getOptionNumber(normalized, 'initialValue', 0x0000, 0xFFFF)
  262. const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
  263. let crc = initialValue
  264. toByteArray(bytes).forEach((byte) => {
  265. crc = (crc >> 8) ^ CRC16_MODBUS_TABLE[(crc ^ byte) & 0xFF]
  266. })
  267. return (crc ^ finalXor) & 0xFFFF
  268. }
  269. function appendCrc16Ibm(bytes, options = {}) {
  270. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW, true)
  271. return appendChecksum16(bytes, crc16Ibm(bytes, normalized), normalized)
  272. }
  273. function hasValidCrc16Ibm(bytes, options = {}) {
  274. const frame = toByteArray(bytes)
  275. if (frame.length < 4) return false
  276. return hasValidChecksum16(frame, crc16Ibm, normalizeChecksumOptions(options, BYTE_ORDER_LOW, true))
  277. }
  278. function crc16Modbus(bytes, options = {}) {
  279. const source = getSourceOptions(options)
  280. const normalized = {
  281. ...normalizeChecksumOptions(options, BYTE_ORDER_LOW),
  282. initialValue: hasOwnOption(source, 'initialValue') ? source.initialValue : 0xFFFF
  283. }
  284. return crc16Ibm(bytes, normalized)
  285. }
  286. function appendCrc16Modbus(bytes, options = {}) {
  287. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_LOW, true)
  288. return appendChecksum16(bytes, crc16Modbus(bytes, normalized), normalized)
  289. }
  290. function hasValidCrc16Modbus(bytes, options = {}) {
  291. const frame = toByteArray(bytes)
  292. if (frame.length < 4) return false
  293. return hasValidChecksum16(frame, crc16Modbus, normalizeChecksumOptions(options, BYTE_ORDER_LOW, true))
  294. }
  295. function crc16Ccitt(bytes, options = {}) {
  296. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH)
  297. const initialValue = getOptionNumber(normalized, 'initialValue', CRC16_CCITT_INIT, 0xFFFF)
  298. const finalXor = getOptionNumber(normalized, 'finalXor', 0x0000, 0xFFFF)
  299. let crc = initialValue
  300. toByteArray(bytes).forEach((byte) => {
  301. crc = ((crc << 8) ^ CRC16_CCITT_TABLE[((crc >> 8) ^ byte) & 0xFF]) & 0xFFFF
  302. })
  303. return (crc ^ finalXor) & 0xFFFF
  304. }
  305. function appendCrc16Ccitt(bytes, options = {}) {
  306. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
  307. return appendChecksum16(bytes, crc16Ccitt(bytes, normalized), normalized)
  308. }
  309. function hasValidCrc16Ccitt(bytes, options = {}) {
  310. const frame = toByteArray(bytes)
  311. if (frame.length < 4) return false
  312. return hasValidChecksum16(frame, crc16Ccitt, normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true))
  313. }
  314. function crc16Xmodem(bytes, options = {}) {
  315. const source = getSourceOptions(options)
  316. const normalized = {
  317. ...normalizeChecksumOptions(source, BYTE_ORDER_HIGH),
  318. initialValue: hasOwnOption(source, 'initialValue') ? source.initialValue : 0x0000
  319. }
  320. return crc16Ccitt(bytes, normalized)
  321. }
  322. function appendCrc16Xmodem(bytes, options = {}) {
  323. const normalized = normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true)
  324. return appendChecksum16(bytes, crc16Xmodem(bytes, normalized), normalized)
  325. }
  326. function hasValidCrc16Xmodem(bytes, options = {}) {
  327. const frame = toByteArray(bytes)
  328. if (frame.length < 4) return false
  329. return hasValidChecksum16(frame, crc16Xmodem, normalizeChecksumOptions(options, BYTE_ORDER_HIGH, true))
  330. }
  331. const CRC_ALGORITHM_PRESETS = [
  332. { key: 'crc-8', label: 'CRC-8', width: 8, poly: '07', init: '00', xorOut: '00', reflectIn: false, reflectOut: false },
  333. { key: 'crc-8-itu', label: 'CRC-8-ITU', width: 8, poly: '07', init: '00', xorOut: '55', reflectIn: false, reflectOut: false },
  334. { key: 'crc-8-rohc', label: 'CRC-8-ROHC', width: 8, poly: '07', init: 'FF', xorOut: '00', reflectIn: true, reflectOut: true },
  335. { key: 'crc-8-maxim', label: 'CRC-8-MAXIM', width: 8, poly: '31', init: '00', xorOut: '00', reflectIn: true, reflectOut: true },
  336. { key: 'crc-16-ibm', label: 'CRC-16-IBM', width: 16, poly: '8005', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
  337. { key: 'crc-16-usb', label: 'CRC-16-USB', width: 16, poly: '8005', init: 'FFFF', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
  338. { key: 'crc-16-maxim', label: 'CRC-16-MAXIM', width: 16, poly: '8005', init: '0000', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
  339. { key: 'crc-16-ccitt', label: 'CRC-16-CCITT', width: 16, poly: '1021', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
  340. { key: 'crc-16-ccitt-false', label: 'CRC-16-CCITT-FALSE', width: 16, poly: '1021', init: 'FFFF', xorOut: '0000', reflectIn: false, reflectOut: false },
  341. { key: 'crc-16-x25', label: 'CRC-16-X25', width: 16, poly: '1021', init: 'FFFF', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
  342. { key: 'crc-16-xmodem', label: 'CRC-16-XMODEM', width: 16, poly: '1021', init: '0000', xorOut: '0000', reflectIn: false, reflectOut: false },
  343. { key: 'crc-16-xmodem2', label: 'CRC-16-XMODEM2', width: 16, poly: '8408', init: '0000', xorOut: '0000', reflectIn: true, reflectOut: true },
  344. { key: 'crc-16-dnp', label: 'CRC-16-DNP', width: 16, poly: '3D65', init: '0000', xorOut: 'FFFF', reflectIn: true, reflectOut: true },
  345. { key: 'crc-24-q', label: 'CRC-24-Q', width: 24, poly: '864CFB', init: '000000', xorOut: '000000', reflectIn: false, reflectOut: false },
  346. { key: 'crc-32', label: 'CRC-32', width: 32, poly: '04C11DB7', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
  347. { key: 'crc-32-c', label: 'CRC-32-C', width: 32, poly: '1EDC6F41', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
  348. { key: 'crc-32-koopman', label: 'CRC-32-KOOPMAN', width: 32, poly: '741B8CD7', init: 'FFFFFFFF', xorOut: 'FFFFFFFF', reflectIn: true, reflectOut: true },
  349. { key: 'crc-32-mpeg-2', label: 'CRC-32-MPEG-2', width: 32, poly: '04C11DB7', init: 'FFFFFFFF', xorOut: '00000000', reflectIn: false, reflectOut: false },
  350. { key: 'crc-64-iso', label: 'CRC-64-ISO', width: 64, poly: '000000000000001B', init: 'FFFFFFFFFFFFFFFF', xorOut: 'FFFFFFFFFFFFFFFF', reflectIn: true, reflectOut: true },
  351. { key: 'crc-64-ecma', label: 'CRC-64-ECMA', width: 64, poly: '42F0E1EBA9EA3693', init: 'FFFFFFFFFFFFFFFF', xorOut: 'FFFFFFFFFFFFFFFF', reflectIn: true, reflectOut: true },
  352. { key: 'custom', label: '自定义', width: 16, poly: '1021', init: 'FFFF', xorOut: '0000', reflectIn: false, reflectOut: false, custom: true }
  353. ]
  354. function maskForWidth(width) {
  355. const bitWidth = Number(width)
  356. if (!Number.isInteger(bitWidth) || bitWidth < 1 || bitWidth > 64) {
  357. throw new Error('CRC 位宽需为 1 - 64')
  358. }
  359. return (1n << BigInt(bitWidth)) - 1n
  360. }
  361. function normalizeHexText(value, fallback = '0') {
  362. const text = String(value === undefined || value === null ? '' : value).trim()
  363. if (!text) return fallback
  364. return text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  365. }
  366. function parseHexBigInt(value, label, fallback = '0') {
  367. if (typeof value === 'bigint') return value
  368. if (typeof value === 'number' && Number.isFinite(value)) return BigInt(Math.max(0, Math.trunc(value)))
  369. const hexText = normalizeHexText(value, fallback)
  370. if (!/^[0-9A-F]+$/i.test(hexText)) {
  371. throw new Error(`${label}需为十六进制`)
  372. }
  373. return BigInt(`0x${hexText}`)
  374. }
  375. function reflectBits(value, width) {
  376. let source = BigInt(value)
  377. let reflected = 0n
  378. for (let index = 0; index < width; index += 1) {
  379. reflected = (reflected << 1n) | (source & 1n)
  380. source >>= 1n
  381. }
  382. return reflected
  383. }
  384. function normalizeCrcConfig(config = {}) {
  385. const width = clampInteger(config.width, 1, 64, 16)
  386. const mask = maskForWidth(width)
  387. const polyValue = config.poly !== undefined ? config.poly : config.polynomial
  388. const initValue = config.init !== undefined ? config.init : config.initialValue
  389. const xorValue = config.xorOut !== undefined ? config.xorOut : config.finalXor
  390. const presetKey = String(config.key || config.presetKey || '')
  391. return {
  392. finalXor: parseHexBigInt(xorValue, '结果异或值', '0') & mask,
  393. initialValue: parseHexBigInt(initValue, '初始值', '0') & mask,
  394. mask,
  395. presetKey,
  396. polynomial: parseHexBigInt(polyValue, 'Poly', '0') & mask,
  397. reflectIn: !!(config.reflectIn || config.refin),
  398. reflectOut: !!(config.reflectOut || config.refout),
  399. useLookupTable: config.useLookupTable === true || (
  400. !!presetKey && presetKey !== 'custom' && (width === 8 || width === 16)
  401. ),
  402. width
  403. }
  404. }
  405. function computeCrcTable(bytes, normalized) {
  406. if (!normalized.useLookupTable) return null
  407. const width = normalized.width
  408. const mask = getCrcNumberMask(width)
  409. if (!mask) return null
  410. const table = getMsbCrcTable(width, Number(normalized.polynomial & BigInt(mask)))
  411. const shift = width - 8
  412. let crc = Number(normalized.initialValue & BigInt(mask))
  413. toByteArray(bytes).forEach((sourceByte) => {
  414. const byte = normalized.reflectIn
  415. ? REFLECT_BYTE_TABLE[sourceByte & 0xFF]
  416. : (sourceByte & 0xFF)
  417. const tableIndex = shift > 0
  418. ? (((crc >> shift) ^ byte) & 0xFF)
  419. : ((crc ^ byte) & 0xFF)
  420. crc = shift > 0
  421. ? (((crc << 8) & mask) ^ table[tableIndex])
  422. : table[tableIndex]
  423. crc &= mask
  424. })
  425. if (normalized.reflectOut) {
  426. crc = reflectNumberBits(crc, width) & mask
  427. }
  428. return BigInt((crc ^ Number(normalized.finalXor & BigInt(mask))) & mask)
  429. }
  430. function computeCrc(bytes, config = {}) {
  431. const normalized = normalizeCrcConfig(config)
  432. const tableValue = computeCrcTable(bytes, normalized)
  433. if (tableValue !== null) return tableValue
  434. const topBit = 1n << BigInt(normalized.width - 1)
  435. let crc = normalized.initialValue
  436. toByteArray(bytes).forEach((sourceByte) => {
  437. const byte = normalized.reflectIn
  438. ? Number(reflectBits(BigInt(sourceByte & 0xFF), 8))
  439. : (sourceByte & 0xFF)
  440. for (let bitMask = 0x80; bitMask > 0; bitMask >>= 1) {
  441. let bitSet = (crc & topBit) !== 0n
  442. crc = (crc << 1n) & normalized.mask
  443. if (byte & bitMask) {
  444. bitSet = !bitSet
  445. }
  446. if (bitSet) {
  447. crc = (crc ^ normalized.polynomial) & normalized.mask
  448. }
  449. }
  450. })
  451. if (normalized.reflectOut) {
  452. crc = reflectBits(crc, normalized.width) & normalized.mask
  453. }
  454. return (crc ^ normalized.finalXor) & normalized.mask
  455. }
  456. function formatCrcHex(value, width) {
  457. const hexLength = Math.max(1, Math.ceil(Number(width || 1) / 4))
  458. return `0x${BigInt(value).toString(16).toUpperCase().padStart(hexLength, '0')}`
  459. }
  460. function formatCrcBin(value, width) {
  461. return BigInt(value).toString(2).padStart(Number(width || 1), '0')
  462. }
  463. function crcValueToBytes(value, width) {
  464. const byteLength = Math.max(1, Math.ceil(Number(width || 1) / 8))
  465. const result = []
  466. let source = BigInt(value)
  467. for (let index = byteLength - 1; index >= 0; index -= 1) {
  468. result[index] = Number(source & 0xFFn)
  469. source >>= 8n
  470. }
  471. return result
  472. }
  473. function calculateCrc(bytes, config = {}) {
  474. const normalized = normalizeCrcConfig(config)
  475. const value = computeCrc(bytes, normalized)
  476. const resultBytes = crcValueToBytes(value, normalized.width)
  477. return {
  478. base64: bytesToBase64(resultBytes),
  479. bin: formatCrcBin(value, normalized.width),
  480. bytes: resultBytes,
  481. hex: formatCrcHex(value, normalized.width),
  482. value,
  483. width: normalized.width
  484. }
  485. }
  486. module.exports = {
  487. BYTE_ORDER_HIGH,
  488. BYTE_ORDER_LOW,
  489. CRC_ALGORITHM_PRESETS,
  490. appendChecksum16: appendChecksum16Value,
  491. appendChecksum8,
  492. appendCrc16Ccitt,
  493. appendCrc16Ibm,
  494. appendCrc16Modbus,
  495. appendCrc16Xmodem,
  496. appendCrc8,
  497. appendLrc8,
  498. appendXor8,
  499. bytesToBase64,
  500. calculateCrc,
  501. checksum16,
  502. checksum8,
  503. computeCrc,
  504. crc16Ccitt,
  505. crc16Ibm,
  506. crc16Modbus,
  507. crc16Xmodem,
  508. crc8,
  509. crcValueToBytes,
  510. formatCrcBin,
  511. formatCrcHex,
  512. hasValidChecksum16: hasValidChecksum16Value,
  513. hasValidChecksum8: hasValidChecksum8Value,
  514. hasValidCrc16Ccitt,
  515. hasValidCrc16Ibm,
  516. hasValidCrc16Modbus,
  517. hasValidCrc16Xmodem,
  518. hasValidCrc8,
  519. hasValidLrc8,
  520. hasValidXor8,
  521. lrc8,
  522. normalizeCrcConfig,
  523. readChecksum16,
  524. xor8
  525. }