扩充 CRC 预设并发布日期版本 2026.8.22
This commit is contained in:
+84
-28
@@ -1,5 +1,12 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { calculateCrc, calculateCrcChunks, CRC_PRESETS, formatCrc } from '../src/lib/crc'
|
||||
import {
|
||||
calculateCrc,
|
||||
calculateCrcChunks,
|
||||
CRC_PRESETS,
|
||||
DEFAULT_CRC_PRESET,
|
||||
formatCrc,
|
||||
type CrcPreset,
|
||||
} from '../src/lib/crc'
|
||||
import {
|
||||
calculateCrcHexInput,
|
||||
calculateCrcReport,
|
||||
@@ -16,45 +23,76 @@ function intelHexRecord(address: number, data: number[]): string {
|
||||
return `:${[...bytes, checksum].map((byte) => byte.toString(16).padStart(2, '0')).join('').toUpperCase()}`
|
||||
}
|
||||
|
||||
const STANDARD_CRC_PRESETS = [
|
||||
{ id: 'crc-32-iso-hdlc', name: 'CRC-32/ISO-HDLC', width: 32, polynomial: 0x04c11db7n, initialValue: 0xffffffffn, inputReflected: true, outputReflected: true, xorOut: 0xffffffffn, check: 0xcbf43926n },
|
||||
{ id: 'crc-32-iscsi', name: 'CRC-32/ISCSI (CRC-32C)', width: 32, polynomial: 0x1edc6f41n, initialValue: 0xffffffffn, inputReflected: true, outputReflected: true, xorOut: 0xffffffffn, check: 0xe3069283n },
|
||||
{ id: 'crc-32-mpeg-2', name: 'CRC-32/MPEG-2', width: 32, polynomial: 0x04c11db7n, initialValue: 0xffffffffn, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0x0376e6e7n },
|
||||
{ id: 'crc-16-arc', name: 'CRC-16/ARC', width: 16, polynomial: 0x8005n, initialValue: 0n, inputReflected: true, outputReflected: true, xorOut: 0n, check: 0xbb3dn },
|
||||
{ id: 'crc-16-dnp', name: 'CRC-16/DNP', width: 16, polynomial: 0x3d65n, initialValue: 0n, inputReflected: true, outputReflected: true, xorOut: 0xffffn, check: 0xea82n },
|
||||
{ id: 'crc-16-ccitt-false', name: 'CRC-16/IBM-3740 (CCITT-FALSE)', width: 16, polynomial: 0x1021n, initialValue: 0xffffn, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0x29b1n },
|
||||
{ id: 'crc-16-ibm-sdlc', name: 'CRC-16/IBM-SDLC (X-25)', width: 16, polynomial: 0x1021n, initialValue: 0xffffn, inputReflected: true, outputReflected: true, xorOut: 0xffffn, check: 0x906en },
|
||||
{ id: 'crc-16-kermit', name: 'CRC-16/KERMIT (CCITT)', width: 16, polynomial: 0x1021n, initialValue: 0n, inputReflected: true, outputReflected: true, xorOut: 0n, check: 0x2189n },
|
||||
{ id: 'crc-16-modbus', name: 'CRC-16/MODBUS', width: 16, polynomial: 0x8005n, initialValue: 0xffffn, inputReflected: true, outputReflected: true, xorOut: 0n, check: 0x4b37n },
|
||||
{ id: 'crc-16-usb', name: 'CRC-16/USB', width: 16, polynomial: 0x8005n, initialValue: 0xffffn, inputReflected: true, outputReflected: true, xorOut: 0xffffn, check: 0xb4c8n },
|
||||
{ id: 'crc-16-xmodem', name: 'CRC-16/XMODEM', width: 16, polynomial: 0x1021n, initialValue: 0n, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0x31c3n },
|
||||
{ id: 'crc-8', name: 'CRC-8/SMBUS (CRC-8)', width: 8, polynomial: 0x07n, initialValue: 0n, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0xf4n },
|
||||
{ id: 'crc-8-maxim-dow', name: 'CRC-8/MAXIM-DOW', width: 8, polynomial: 0x31n, initialValue: 0n, inputReflected: true, outputReflected: true, xorOut: 0n, check: 0xa1n },
|
||||
{ id: 'crc-8-sae-j1850', name: 'CRC-8/SAE-J1850', width: 8, polynomial: 0x1dn, initialValue: 0xffn, inputReflected: false, outputReflected: false, xorOut: 0xffn, check: 0x4bn },
|
||||
{ id: 'crc-64-ecma-182', name: 'CRC-64/ECMA-182', width: 64, polynomial: 0x42f0e1eba9ea3693n, initialValue: 0n, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0x6c40df5f0b497347n },
|
||||
{ id: 'crc-64-xz', name: 'CRC-64/XZ', width: 64, polynomial: 0x42f0e1eba9ea3693n, initialValue: 0xffffffffffffffffn, inputReflected: true, outputReflected: true, xorOut: 0xffffffffffffffffn, check: 0x995dc9bbdf1939fan },
|
||||
] as const satisfies readonly CrcPreset[]
|
||||
|
||||
function presetById(id: string): CrcPreset {
|
||||
const preset = CRC_PRESETS.find((candidate) => candidate.id === id)
|
||||
if (!preset) throw new Error(`Missing test preset: ${id}`)
|
||||
return preset
|
||||
}
|
||||
|
||||
describe('CRC', () => {
|
||||
it('publishes the reviewed canonical preset names and parameters', () => {
|
||||
expect(CRC_PRESETS).toEqual(STANDARD_CRC_PRESETS)
|
||||
expect(DEFAULT_CRC_PRESET).toEqual(STANDARD_CRC_PRESETS[0])
|
||||
expect(new Set(CRC_PRESETS.map((preset) => preset.id)).size).toBe(CRC_PRESETS.length)
|
||||
expect(new Set(CRC_PRESETS.map((preset) => preset.name)).size).toBe(CRC_PRESETS.length)
|
||||
expect(new Set(CRC_PRESETS.map((preset) => preset.width))).toEqual(new Set([8, 16, 32, 64]))
|
||||
|
||||
const parameterSignatures = CRC_PRESETS.map((preset) => [
|
||||
preset.width,
|
||||
preset.polynomial,
|
||||
preset.initialValue,
|
||||
preset.inputReflected,
|
||||
preset.outputReflected,
|
||||
preset.xorOut,
|
||||
].join(':'))
|
||||
expect(new Set(parameterSignatures).size).toBe(CRC_PRESETS.length)
|
||||
})
|
||||
|
||||
it('matches standard check values for 123456789', () => {
|
||||
const input = textToBytes('123456789')
|
||||
for (const preset of CRC_PRESETS) {
|
||||
for (const preset of STANDARD_CRC_PRESETS) {
|
||||
expect(calculateCrc(input, preset), preset.name).toBe(preset.check)
|
||||
expect(formatCrc(preset.check, preset.width)).toHaveLength(Math.ceil(preset.width / 4))
|
||||
}
|
||||
})
|
||||
|
||||
it('supports CRC-64 / ECMA-182 parameters', () => {
|
||||
expect(calculateCrc(textToBytes('123456789'), {
|
||||
width: 64,
|
||||
polynomial: 0x42f0e1eba9ea3693n,
|
||||
initialValue: 0n,
|
||||
inputReflected: false,
|
||||
outputReflected: false,
|
||||
xorOut: 0n,
|
||||
})).toBe(0x6c40df5f0b497347n)
|
||||
})
|
||||
|
||||
it('supports reflected CRC-64 / XZ parameters', () => {
|
||||
expect(calculateCrc(textToBytes('123456789'), {
|
||||
width: 64,
|
||||
polynomial: 0x42f0e1eba9ea3693n,
|
||||
initialValue: 0xffffffffffffffffn,
|
||||
inputReflected: true,
|
||||
outputReflected: true,
|
||||
xorOut: 0xffffffffffffffffn,
|
||||
})).toBe(0x995dc9bbdf1939fan)
|
||||
it('handles empty input and fixed-width result boundaries', () => {
|
||||
for (const preset of STANDARD_CRC_PRESETS) {
|
||||
const mask = (1n << BigInt(preset.width)) - 1n
|
||||
expect(calculateCrc(new Uint8Array(), preset), preset.name).toBe((preset.initialValue ^ preset.xorOut) & mask)
|
||||
}
|
||||
expect(formatCrc(0n, 8)).toBe('00')
|
||||
expect(formatCrc(0xffffffffffffffffn, 64)).toBe('FFFFFFFFFFFFFFFF')
|
||||
})
|
||||
|
||||
it('calculates chunked data as one continuous byte stream', () => {
|
||||
const preset = CRC_PRESETS[0]
|
||||
const chunks = [textToBytes('123'), textToBytes('456'), textToBytes('789')]
|
||||
expect(calculateCrcChunks(chunks, preset)).toBe(calculateCrc(textToBytes('123456789'), preset))
|
||||
for (const preset of [DEFAULT_CRC_PRESET, presetById('crc-64-xz')]) {
|
||||
expect(calculateCrcChunks(chunks, preset)).toBe(calculateCrc(textToBytes('123456789'), preset))
|
||||
}
|
||||
})
|
||||
|
||||
it('calculates inline Hex with the same separators used by other tools', () => {
|
||||
const preset = CRC_PRESETS[0]
|
||||
const preset = DEFAULT_CRC_PRESET
|
||||
const inputs = [
|
||||
'A51F7ADD',
|
||||
'A5 1F 7A DD',
|
||||
@@ -71,8 +109,15 @@ describe('CRC', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('calculates the CCITT/KERMIT preset through the inline Hex path', () => {
|
||||
expect(calculateCrcHexInput('31 32 33 34 35 36 37 38 39', presetById('crc-16-kermit'))).toEqual({
|
||||
combinedResult: '2189',
|
||||
totalBytes: 9,
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects empty, malformed, and oversized inline Hex', () => {
|
||||
const preset = CRC_PRESETS[0]
|
||||
const preset = DEFAULT_CRC_PRESET
|
||||
expect(() => calculateCrcHexInput(' ', preset)).toThrow('请输入 Hex 数据')
|
||||
expect(() => calculateCrcHexInput('ABC', preset)).toThrow('偶数')
|
||||
expect(() => calculateCrcHexInput('A5,,1F', preset)).toThrow('片段缺少')
|
||||
@@ -82,15 +127,17 @@ describe('CRC', () => {
|
||||
})
|
||||
|
||||
it('rejects malformed runtime parameters instead of coercing them', () => {
|
||||
const preset = CRC_PRESETS[0]
|
||||
const preset = DEFAULT_CRC_PRESET
|
||||
expect(() => calculateCrc(new Uint8Array(), { ...preset, inputReflected: undefined as never })).toThrow('布尔值')
|
||||
expect(() => calculateCrc(new Uint8Array(), { ...preset, polynomial: 7 as never })).toThrow('BigInt')
|
||||
expect(() => formatCrc(0x100n, 8)).toThrow('超出当前位宽')
|
||||
expect(() => formatCrc(0n, 7)).toThrow('位宽')
|
||||
expect(() => calculateCrc(new Uint8Array(), { ...preset, width: 65 })).toThrow('位宽')
|
||||
expect(() => calculateCrc(new Uint8Array(), { ...preset, polynomial: 0n })).toThrow('多项式')
|
||||
})
|
||||
|
||||
it('reports both the combined CRC and each block CRC', () => {
|
||||
const parameters = CRC_PRESETS.find((preset) => preset.id === 'crc-32-iso-hdlc')!
|
||||
const parameters = DEFAULT_CRC_PRESET
|
||||
const blocks = [
|
||||
{ startAddress: 0x1000, data: textToBytes('1234') },
|
||||
{ startAddress: 0x2000, data: textToBytes('56789') },
|
||||
@@ -102,6 +149,15 @@ describe('CRC', () => {
|
||||
{ startAddress: 0x2000, endAddress: 0x2004, length: 5, crc: formatCrc(calculateCrc(blocks[1].data, parameters), 32) },
|
||||
])
|
||||
})
|
||||
|
||||
it('preserves full 64-bit CRC values in block reports', () => {
|
||||
const parameters = presetById('crc-64-xz')
|
||||
const report = calculateCrcReport([{ startAddress: 0, data: textToBytes('123456789') }], parameters)
|
||||
expect(report.combinedResult).toBe('995DC9BBDF1939FA')
|
||||
expect(report.blockResults).toEqual([
|
||||
{ startAddress: 0, endAddress: 8, length: 9, crc: '995DC9BBDF1939FA' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Intel HEX', () => {
|
||||
|
||||
Reference in New Issue
Block a user