crc-tool.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. const {
  2. CRC_ALGORITHM_PRESETS,
  3. calculateCrc
  4. } = require('./crc')
  5. const {
  6. HASH_ALGORITHM_PRESETS,
  7. calculateHash
  8. } = require('./hash')
  9. const {
  10. formatBytes,
  11. loadSelectedFile
  12. } = require('./file-service')
  13. const {
  14. stringToUtf8Bytes
  15. } = require('./binary-utils')
  16. const INPUT_TYPE_OPTIONS = [
  17. { key: 'hex', label: 'HEX' },
  18. { key: 'string', label: 'String' },
  19. { key: 'base64', label: 'Base64' }
  20. ]
  21. const ALGORITHM_PRESETS = CRC_ALGORITHM_PRESETS
  22. .map((preset) => ({ ...preset, kind: 'crc' }))
  23. .concat(HASH_ALGORITHM_PRESETS)
  24. const CRC_CONFIG_FIELDS = [
  25. 'crcInitialValue',
  26. 'crcPoly',
  27. 'crcWidth',
  28. 'crcXorOut'
  29. ]
  30. function clonePreset(preset) {
  31. return {
  32. ...preset
  33. }
  34. }
  35. function getPreset(index) {
  36. return clonePreset(ALGORITHM_PRESETS[Number(index)] || ALGORITHM_PRESETS[0])
  37. }
  38. function getCustomPresetIndex() {
  39. return Math.max(0, ALGORITHM_PRESETS.findIndex((preset) => preset.custom))
  40. }
  41. function getInputType(index) {
  42. return INPUT_TYPE_OPTIONS[Number(index)] || INPUT_TYPE_OPTIONS[0]
  43. }
  44. function createPresetState(presetIndex = 0) {
  45. const preset = getPreset(presetIndex)
  46. const kind = preset.kind || 'crc'
  47. return {
  48. crcAlgorithmCollapsed: !(preset.custom || kind === 'hmac' || kind === 'pbkdf2'),
  49. crcAlgorithmKind: kind,
  50. crcInitialValue: preset.init || 'FFFF',
  51. crcPoly: preset.poly || '1021',
  52. crcPresetIndex: Number(presetIndex),
  53. crcReflectIn: !!preset.reflectIn,
  54. crcReflectOut: !!preset.reflectOut,
  55. crcShowBinResult: kind === 'crc',
  56. crcShowCrcConfig: kind === 'crc',
  57. crcShowHmacKey: kind === 'hmac',
  58. crcShowPbkdf2Config: kind === 'pbkdf2',
  59. crcWidth: String(preset.width || 16),
  60. crcXorOut: preset.xorOut || '0000'
  61. }
  62. }
  63. function createInitialState() {
  64. return {
  65. ...createPresetState(0),
  66. crcDataLengthText: '0 bytes',
  67. crcDataText: '',
  68. crcErrorText: '',
  69. crcFileName: '',
  70. crcFileSizeText: '',
  71. crcHmacKey: '',
  72. crcInputTypeIndex: 0,
  73. crcInputTypeOptions: INPUT_TYPE_OPTIONS,
  74. crcPbkdf2Iterations: '1000',
  75. crcPbkdf2Length: '32',
  76. crcPbkdf2Salt: '',
  77. crcPresetOptions: ALGORITHM_PRESETS.map(clonePreset),
  78. crcResultBase64: '--',
  79. crcResultBin: '--',
  80. crcResultBinLines: splitBinaryResult('--'),
  81. crcResultHex: '--'
  82. }
  83. }
  84. function normalizeHexData(text) {
  85. return String(text || '')
  86. .replace(/0x/gi, '')
  87. .replace(/[\s,_:-]/g, '')
  88. }
  89. function parseHexBytes(text) {
  90. const hexText = normalizeHexData(text)
  91. if (!hexText) return []
  92. if (!/^[0-9A-F]+$/i.test(hexText)) throw new Error('HEX 数据包含非法字符')
  93. if (hexText.length % 2 !== 0) throw new Error('HEX 数据长度必须为偶数字符')
  94. const bytes = []
  95. for (let index = 0; index < hexText.length; index += 2) {
  96. bytes.push(parseInt(hexText.slice(index, index + 2), 16))
  97. }
  98. return bytes
  99. }
  100. function parseBase64Bytes(text) {
  101. const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  102. let normalized = String(text || '').replace(/\s/g, '')
  103. if (!normalized) return []
  104. if (normalized.length % 4 === 1 || /[^A-Za-z0-9+/=]/.test(normalized)) {
  105. throw new Error('Base64 数据无效')
  106. }
  107. while (normalized.length % 4 !== 0) {
  108. normalized += '='
  109. }
  110. const bytes = []
  111. for (let index = 0; index < normalized.length; index += 4) {
  112. const chars = normalized.slice(index, index + 4)
  113. const values = chars.split('').map((char) => (char === '=' ? 0 : alphabet.indexOf(char)))
  114. if (values.some((value) => value < 0)) throw new Error('Base64 数据无效')
  115. const triple = (values[0] << 18) | (values[1] << 12) | (values[2] << 6) | values[3]
  116. bytes.push((triple >> 16) & 0xFF)
  117. if (chars[2] !== '=') bytes.push((triple >> 8) & 0xFF)
  118. if (chars[3] !== '=') bytes.push(triple & 0xFF)
  119. }
  120. return bytes
  121. }
  122. function parseInputBytes(dataText, inputTypeIndex) {
  123. const inputType = getInputType(inputTypeIndex)
  124. if (inputType.key === 'hex') return parseHexBytes(dataText)
  125. if (inputType.key === 'base64') return parseBase64Bytes(dataText)
  126. return stringToUtf8Bytes(dataText)
  127. }
  128. function splitBinaryResult(value) {
  129. const text = String(value || '--')
  130. if (text === '--' || text.length <= 32) return [
  131. {
  132. id: 'bin-line-0',
  133. text
  134. }
  135. ]
  136. const lineLength = Math.ceil(text.length / 2)
  137. return [
  138. {
  139. id: 'bin-line-0',
  140. text: text.slice(0, lineLength)
  141. },
  142. {
  143. id: 'bin-line-1',
  144. text: text.slice(lineLength)
  145. }
  146. ]
  147. }
  148. function getConfigFromState(state) {
  149. const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}
  150. return {
  151. init: state.crcInitialValue,
  152. key: preset.key || '',
  153. poly: state.crcPoly,
  154. reflectIn: !!state.crcReflectIn,
  155. reflectOut: !!state.crcReflectOut,
  156. useLookupTable: !!preset.key && !preset.custom,
  157. width: state.crcWidth,
  158. xorOut: state.crcXorOut
  159. }
  160. }
  161. function getHashConfigFromState(state) {
  162. const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}
  163. return {
  164. hmacKey: state.crcHmacKey,
  165. key: preset.key || '',
  166. pbkdf2Iterations: state.crcPbkdf2Iterations,
  167. pbkdf2Length: state.crcPbkdf2Length,
  168. pbkdf2Salt: state.crcPbkdf2Salt
  169. }
  170. }
  171. function calculateFromState(state, fileBytes) {
  172. const bytes = fileBytes
  173. ? Array.prototype.slice.call(fileBytes)
  174. : parseInputBytes(state.crcDataText, state.crcInputTypeIndex)
  175. const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {}
  176. const result = (preset.kind || 'crc') === 'crc'
  177. ? calculateCrc(bytes, getConfigFromState(state))
  178. : calculateHash(bytes, getHashConfigFromState(state))
  179. return {
  180. crcDataLengthText: formatBytes(bytes.length),
  181. crcErrorText: '',
  182. crcResultBase64: result.base64,
  183. crcResultBin: result.bin,
  184. crcResultBinLines: splitBinaryResult(result.bin),
  185. crcResultHex: result.hex
  186. }
  187. }
  188. async function loadFileFromMessage() {
  189. const file = await loadSelectedFile('message')
  190. return {
  191. bytes: file.bytes,
  192. name: file.name,
  193. sizeText: file.sizeText
  194. }
  195. }
  196. module.exports = {
  197. calculateFromState,
  198. createInitialState,
  199. createPresetState,
  200. CRC_CONFIG_FIELDS,
  201. getCustomPresetIndex,
  202. loadFileFromMessage
  203. }