153 lines
6.1 KiB
TypeScript
153 lines
6.1 KiB
TypeScript
export interface CrcParameters {
|
|
width: number
|
|
polynomial: bigint
|
|
initialValue: bigint
|
|
inputReflected: boolean
|
|
outputReflected: boolean
|
|
xorOut: bigint
|
|
}
|
|
|
|
export interface CrcPreset extends CrcParameters {
|
|
id: string
|
|
name: string
|
|
check: bigint
|
|
}
|
|
|
|
export const CRC_PRESETS: CrcPreset[] = [
|
|
{ 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-16-ccitt-false', name: 'CRC-16 / CCITT-FALSE', width: 16, polynomial: 0x1021n, initialValue: 0xffffn, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0x29b1n },
|
|
{ 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-arc', name: 'CRC-16 / ARC', width: 16, polynomial: 0x8005n, initialValue: 0n, inputReflected: true, outputReflected: true, xorOut: 0n, check: 0xbb3dn },
|
|
{ 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', width: 8, polynomial: 0x07n, initialValue: 0n, inputReflected: false, outputReflected: false, xorOut: 0n, check: 0xf4n },
|
|
]
|
|
|
|
function reflectBigInt(value: bigint, width: number): bigint {
|
|
let reflected = 0n
|
|
for (let bit = 0; bit < width; bit += 1) {
|
|
if (value & (1n << BigInt(bit))) reflected |= 1n << BigInt(width - 1 - bit)
|
|
}
|
|
return reflected
|
|
}
|
|
|
|
function validateParameters(parameters: CrcParameters) {
|
|
if (!Number.isInteger(parameters.width) || parameters.width < 8 || parameters.width > 64) {
|
|
throw new Error('CRC 位宽必须是 8 至 64 的整数')
|
|
}
|
|
const mask = (1n << BigInt(parameters.width)) - 1n
|
|
if (parameters.polynomial <= 0n || parameters.polynomial > mask) throw new Error('CRC 多项式超出当前位宽')
|
|
if (parameters.initialValue < 0n || parameters.initialValue > mask) throw new Error('CRC 初始值超出当前位宽')
|
|
if (parameters.xorOut < 0n || parameters.xorOut > mask) throw new Error('CRC 异或输出值超出当前位宽')
|
|
}
|
|
|
|
function reflectNumber(value: number, width: number): number {
|
|
let current = value >>> 0
|
|
let reflected = 0
|
|
for (let bit = 0; bit < width; bit += 1) {
|
|
reflected = ((reflected * 2) + (current & 1)) >>> 0
|
|
current >>>= 1
|
|
}
|
|
return reflected
|
|
}
|
|
|
|
function calculateNumberCrc(chunks: Iterable<Uint8Array>, parameters: CrcParameters): bigint {
|
|
const width = parameters.width
|
|
const mask = width === 32 ? 0xffffffff : (2 ** width) - 1
|
|
const trim = (value: number) => width === 32 ? value >>> 0 : (value & mask) >>> 0
|
|
const polynomial = Number(parameters.polynomial) >>> 0
|
|
const table = new Uint32Array(256)
|
|
let remainder = trim(Number(parameters.initialValue))
|
|
|
|
if (parameters.inputReflected) {
|
|
const reflectedPolynomial = reflectNumber(polynomial, width)
|
|
for (let index = 0; index < 256; index += 1) {
|
|
let entry = index
|
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
entry = entry & 1 ? (entry >>> 1) ^ reflectedPolynomial : entry >>> 1
|
|
}
|
|
table[index] = trim(entry)
|
|
}
|
|
for (const data of chunks) {
|
|
for (const byte of data) {
|
|
remainder = trim((remainder >>> 8) ^ table[(remainder ^ byte) & 0xff])
|
|
}
|
|
}
|
|
} else {
|
|
const topBit = 2 ** (width - 1)
|
|
for (let index = 0; index < 256; index += 1) {
|
|
let entry = trim(index << (width - 8))
|
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
entry = trim(entry & topBit ? (entry << 1) ^ polynomial : entry << 1)
|
|
}
|
|
table[index] = entry
|
|
}
|
|
for (const data of chunks) {
|
|
for (const byte of data) {
|
|
const tableIndex = ((remainder >>> (width - 8)) ^ byte) & 0xff
|
|
remainder = trim((remainder << 8) ^ table[tableIndex])
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameters.inputReflected !== parameters.outputReflected) remainder = reflectNumber(remainder, width)
|
|
return BigInt(trim(remainder ^ Number(parameters.xorOut)))
|
|
}
|
|
|
|
function calculateBigIntCrc(chunks: Iterable<Uint8Array>, parameters: CrcParameters): bigint {
|
|
const width = parameters.width
|
|
const shift = BigInt(width - 8)
|
|
const mask = (1n << BigInt(width)) - 1n
|
|
const table: bigint[] = []
|
|
let remainder = parameters.initialValue & mask
|
|
|
|
if (parameters.inputReflected) {
|
|
const reflectedPolynomial = reflectBigInt(parameters.polynomial, width)
|
|
for (let index = 0; index < 256; index += 1) {
|
|
let entry = BigInt(index)
|
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
entry = entry & 1n ? (entry >> 1n) ^ reflectedPolynomial : entry >> 1n
|
|
}
|
|
table[index] = entry & mask
|
|
}
|
|
for (const data of chunks) {
|
|
for (const byte of data) {
|
|
remainder = ((remainder >> 8n) ^ table[Number((remainder ^ BigInt(byte)) & 0xffn)]) & mask
|
|
}
|
|
}
|
|
} else {
|
|
const topBit = 1n << BigInt(width - 1)
|
|
for (let index = 0; index < 256; index += 1) {
|
|
let entry = BigInt(index) << shift
|
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
entry = entry & topBit ? (entry << 1n) ^ parameters.polynomial : entry << 1n
|
|
entry &= mask
|
|
}
|
|
table[index] = entry
|
|
}
|
|
for (const data of chunks) {
|
|
for (const byte of data) {
|
|
const tableIndex = Number(((remainder >> shift) ^ BigInt(byte)) & 0xffn)
|
|
remainder = ((remainder << 8n) ^ table[tableIndex]) & mask
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameters.inputReflected !== parameters.outputReflected) remainder = reflectBigInt(remainder, width)
|
|
return (remainder ^ parameters.xorOut) & mask
|
|
}
|
|
|
|
export function calculateCrcChunks(chunks: Iterable<Uint8Array>, parameters: CrcParameters): bigint {
|
|
validateParameters(parameters)
|
|
return parameters.width <= 32
|
|
? calculateNumberCrc(chunks, parameters)
|
|
: calculateBigIntCrc(chunks, parameters)
|
|
}
|
|
|
|
export function calculateCrc(data: Uint8Array, parameters: CrcParameters): bigint {
|
|
return calculateCrcChunks([data], parameters)
|
|
}
|
|
|
|
export function formatCrc(value: bigint, width: number): string {
|
|
return value.toString(16).toUpperCase().padStart(Math.ceil(width / 4), '0')
|
|
}
|