const { CRC_ALGORITHM_PRESETS, calculateCrc } = require('./crc') const { HASH_ALGORITHM_PRESETS, calculateHash } = require('./hash') const { formatBytes, loadSelectedFile } = require('./file-service') const { stringToUtf8Bytes } = require('./binary-utils') const INPUT_TYPE_OPTIONS = [ { key: 'hex', label: 'HEX' }, { key: 'string', label: 'String' }, { key: 'base64', label: 'Base64' } ] const ALGORITHM_PRESETS = CRC_ALGORITHM_PRESETS .map((preset) => ({ ...preset, kind: 'crc' })) .concat(HASH_ALGORITHM_PRESETS) const CRC_CONFIG_FIELDS = [ 'crcInitialValue', 'crcPoly', 'crcWidth', 'crcXorOut' ] function clonePreset(preset) { return { ...preset } } function getPreset(index) { return clonePreset(ALGORITHM_PRESETS[Number(index)] || ALGORITHM_PRESETS[0]) } function getCustomPresetIndex() { return Math.max(0, ALGORITHM_PRESETS.findIndex((preset) => preset.custom)) } function getInputType(index) { return INPUT_TYPE_OPTIONS[Number(index)] || INPUT_TYPE_OPTIONS[0] } function createPresetState(presetIndex = 0) { const preset = getPreset(presetIndex) const kind = preset.kind || 'crc' return { crcAlgorithmCollapsed: !(preset.custom || kind === 'hmac' || kind === 'pbkdf2'), crcAlgorithmKind: kind, crcInitialValue: preset.init || 'FFFF', crcPoly: preset.poly || '1021', crcPresetIndex: Number(presetIndex), crcReflectIn: !!preset.reflectIn, crcReflectOut: !!preset.reflectOut, crcShowBinResult: kind === 'crc', crcShowCrcConfig: kind === 'crc', crcShowHmacKey: kind === 'hmac', crcShowPbkdf2Config: kind === 'pbkdf2', crcWidth: String(preset.width || 16), crcXorOut: preset.xorOut || '0000' } } function createInitialState() { return { ...createPresetState(0), crcDataLengthText: '0 bytes', crcDataText: '', crcErrorText: '', crcFileName: '', crcFileSizeText: '', crcHmacKey: '', crcInputTypeIndex: 0, crcInputTypeOptions: INPUT_TYPE_OPTIONS, crcPbkdf2Iterations: '1000', crcPbkdf2Length: '32', crcPbkdf2Salt: '', crcPresetOptions: ALGORITHM_PRESETS.map(clonePreset), crcResultBase64: '--', crcResultBin: '--', crcResultBinLines: splitBinaryResult('--'), crcResultHex: '--' } } function normalizeHexData(text) { return String(text || '') .replace(/0x/gi, '') .replace(/[\s,_:-]/g, '') } function parseHexBytes(text) { const hexText = normalizeHexData(text) if (!hexText) return [] if (!/^[0-9A-F]+$/i.test(hexText)) throw new Error('HEX 数据包含非法字符') if (hexText.length % 2 !== 0) throw new Error('HEX 数据长度必须为偶数字符') const bytes = [] for (let index = 0; index < hexText.length; index += 2) { bytes.push(parseInt(hexText.slice(index, index + 2), 16)) } return bytes } function parseBase64Bytes(text) { const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' let normalized = String(text || '').replace(/\s/g, '') if (!normalized) return [] if (normalized.length % 4 === 1 || /[^A-Za-z0-9+/=]/.test(normalized)) { throw new Error('Base64 数据无效') } while (normalized.length % 4 !== 0) { normalized += '=' } const bytes = [] for (let index = 0; index < normalized.length; index += 4) { const chars = normalized.slice(index, index + 4) const values = chars.split('').map((char) => (char === '=' ? 0 : alphabet.indexOf(char))) if (values.some((value) => value < 0)) throw new Error('Base64 数据无效') const triple = (values[0] << 18) | (values[1] << 12) | (values[2] << 6) | values[3] bytes.push((triple >> 16) & 0xFF) if (chars[2] !== '=') bytes.push((triple >> 8) & 0xFF) if (chars[3] !== '=') bytes.push(triple & 0xFF) } return bytes } function parseInputBytes(dataText, inputTypeIndex) { const inputType = getInputType(inputTypeIndex) if (inputType.key === 'hex') return parseHexBytes(dataText) if (inputType.key === 'base64') return parseBase64Bytes(dataText) return stringToUtf8Bytes(dataText) } function splitBinaryResult(value) { const text = String(value || '--') if (text === '--' || text.length <= 32) return [ { id: 'bin-line-0', text } ] const lineLength = Math.ceil(text.length / 2) return [ { id: 'bin-line-0', text: text.slice(0, lineLength) }, { id: 'bin-line-1', text: text.slice(lineLength) } ] } function getConfigFromState(state) { const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {} return { init: state.crcInitialValue, key: preset.key || '', poly: state.crcPoly, reflectIn: !!state.crcReflectIn, reflectOut: !!state.crcReflectOut, useLookupTable: !!preset.key && !preset.custom, width: state.crcWidth, xorOut: state.crcXorOut } } function getHashConfigFromState(state) { const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {} return { hmacKey: state.crcHmacKey, key: preset.key || '', pbkdf2Iterations: state.crcPbkdf2Iterations, pbkdf2Length: state.crcPbkdf2Length, pbkdf2Salt: state.crcPbkdf2Salt } } function calculateFromState(state, fileBytes) { const bytes = fileBytes ? Array.prototype.slice.call(fileBytes) : parseInputBytes(state.crcDataText, state.crcInputTypeIndex) const preset = (state.crcPresetOptions || [])[Number(state.crcPresetIndex)] || {} const result = (preset.kind || 'crc') === 'crc' ? calculateCrc(bytes, getConfigFromState(state)) : calculateHash(bytes, getHashConfigFromState(state)) return { crcDataLengthText: formatBytes(bytes.length), crcErrorText: '', crcResultBase64: result.base64, crcResultBin: result.bin, crcResultBinLines: splitBinaryResult(result.bin), crcResultHex: result.hex } } async function loadFileFromMessage() { const file = await loadSelectedFile('message') return { bytes: file.bytes, name: file.name, sizeText: file.sizeText } } module.exports = { calculateFromState, createInitialState, createPresetState, CRC_CONFIG_FIELDS, getCustomPresetIndex, loadFileFromMessage }