Initial release of Web Tools 0.0.5
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { calculateCrc, calculateCrcChunks, CRC_PRESETS, formatCrc } from '../src/lib/crc'
|
||||
import { calculateCrcReport } from '../src/lib/crcWorker'
|
||||
import { getFirmwareFormat, parseFirmwareContent } from '../src/lib/firmware'
|
||||
import { concatenateBlocks, parseIntelHex } from '../src/lib/intelHex'
|
||||
import { textToBytes } from '../src/lib/bytes'
|
||||
|
||||
function intelHexRecord(address: number, data: number[]): string {
|
||||
const bytes = [data.length, address >> 8, address & 0xff, 0, ...data]
|
||||
const checksum = (-bytes.reduce((sum, byte) => sum + byte, 0)) & 0xff
|
||||
return `:${[...bytes, checksum].map((byte) => byte.toString(16).padStart(2, '0')).join('').toUpperCase()}`
|
||||
}
|
||||
|
||||
describe('CRC', () => {
|
||||
it('matches standard check values for 123456789', () => {
|
||||
const input = textToBytes('123456789')
|
||||
for (const preset of 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('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))
|
||||
})
|
||||
|
||||
it('reports both the combined CRC and each block CRC', () => {
|
||||
const parameters = CRC_PRESETS.find((preset) => preset.id === 'crc-32-iso-hdlc')!
|
||||
const blocks = [
|
||||
{ startAddress: 0x1000, data: textToBytes('1234') },
|
||||
{ startAddress: 0x2000, data: textToBytes('56789') },
|
||||
]
|
||||
const report = calculateCrcReport(blocks, parameters)
|
||||
expect(report.combinedResult).toBe(formatCrc(parameters.check, parameters.width))
|
||||
expect(report.blockResults).toEqual([
|
||||
{ startAddress: 0x1000, endAddress: 0x1003, length: 4, crc: formatCrc(calculateCrc(blocks[0].data, parameters), 32) },
|
||||
{ startAddress: 0x2000, endAddress: 0x2004, length: 5, crc: formatCrc(calculateCrc(blocks[1].data, parameters), 32) },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('Intel HEX', () => {
|
||||
it('validates records and separates non-contiguous data into blocks', () => {
|
||||
const parsed = parseIntelHex(':0400000001020304F2\n:02001000AABB89\n:00000001FF\n')
|
||||
expect(parsed.totalBytes).toBe(6)
|
||||
expect(parsed.blocks).toHaveLength(2)
|
||||
expect(parsed.blocks[0]).toEqual({ startAddress: 0, data: Uint8Array.from([1, 2, 3, 4]) })
|
||||
expect(parsed.blocks[1]).toEqual({ startAddress: 0x10, data: Uint8Array.from([0xaa, 0xbb]) })
|
||||
expect(concatenateBlocks(parsed.blocks)).toEqual(Uint8Array.from([1, 2, 3, 4, 0xaa, 0xbb]))
|
||||
})
|
||||
|
||||
it('supports extended addresses and reports a missing EOF record', () => {
|
||||
const parsed = parseIntelHex(':020000040001F9\n:040000001122334452\n')
|
||||
expect(parsed.blocks[0].startAddress).toBe(0x10000)
|
||||
expect(parsed.warnings).toEqual(['文件缺少 EOF 记录,已按现有数据计算'])
|
||||
})
|
||||
|
||||
it('rejects invalid checksums and conflicting overlapping data', () => {
|
||||
expect(() => parseIntelHex(':0400000001020304F3\n:00000001FF')).toThrow('校验和错误')
|
||||
expect(() => parseIntelHex(':020000000102FB\n:02000100FF03FB\n:00000001FF')).toThrow('数据冲突')
|
||||
})
|
||||
|
||||
it('merges out-of-order and identical overlapping records without changing bytes', () => {
|
||||
const parsed = parseIntelHex([
|
||||
intelHexRecord(2, [3, 4]),
|
||||
intelHexRecord(0, [1, 2, 3]),
|
||||
':00000001FF',
|
||||
].join('\n'))
|
||||
expect(parsed.blocks).toEqual([{ startAddress: 0, data: Uint8Array.from([1, 2, 3, 4]) }])
|
||||
})
|
||||
|
||||
it('enforces configurable data-record and block limits', () => {
|
||||
const records = [intelHexRecord(0, [1]), intelHexRecord(2, [2]), ':00000001FF'].join('\n')
|
||||
expect(() => parseIntelHex(records, { maxDataRecords: 1 })).toThrow('数据记录不能超过 1 条')
|
||||
expect(() => parseIntelHex(records, { maxBlocks: 1 })).toThrow('数据块不能超过 1 个')
|
||||
expect(() => parseIntelHex(records, { maxBlocks: 0 })).toThrow('必须是正整数')
|
||||
})
|
||||
})
|
||||
|
||||
describe('firmware parsing', () => {
|
||||
it('detects supported extensions case-insensitively', () => {
|
||||
expect(getFirmwareFormat('firmware.HEX')).toBe('Intel HEX')
|
||||
expect(getFirmwareFormat('firmware.ihx')).toBe('Intel HEX')
|
||||
expect(getFirmwareFormat('firmware.bin')).toBe('BIN')
|
||||
expect(() => getFirmwareFormat('firmware.txt')).toThrow('请选择')
|
||||
})
|
||||
|
||||
it('parses Intel HEX and BIN content with strict content types', () => {
|
||||
const hex = parseFirmwareContent('firmware.hex', `${intelHexRecord(0, [0xaa, 0xbb])}\n:00000001FF`)
|
||||
expect(hex.totalBytes).toBe(2)
|
||||
expect(hex.blocks[0].data).toEqual(Uint8Array.from([0xaa, 0xbb]))
|
||||
|
||||
const bin = parseFirmwareContent('firmware.bin', Uint8Array.from([1, 2, 3]))
|
||||
expect(bin).toMatchObject({ format: 'BIN', totalBytes: 3, warnings: [] })
|
||||
expect(() => parseFirmwareContent('firmware.hex', Uint8Array.of(1))).toThrow('必须是文本')
|
||||
expect(() => parseFirmwareContent('firmware.bin', '')).toThrow('必须是二进制')
|
||||
expect(() => parseFirmwareContent('firmware.bin', new Uint8Array())).toThrow('文件为空')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user