122 lines
4.3 KiB
TypeScript
122 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { OnePkgError } from '../errors.js';
|
|
import { QuantumDecoder } from './decoder.js';
|
|
|
|
/*
|
|
* Pinned compcol MIT fixture from tests/quantum.rs at commit
|
|
* 04a6db2aa7bd487a89c559631d79d1b384139f50. The source bytes are the
|
|
* Quantum CFDATA payload from libmspack's mszip_lzx_qtm.cab; the compcol test
|
|
* appends 0xFF, while QuantumDecoder appends that CAB sentinel itself.
|
|
*/
|
|
const QTM_TEXT = Uint8Array.from([
|
|
0xd6, 0x06, 0x69, 0x0b, 0xcb, 0x47, 0xf0, 0x2c, 0x2a, 0x3a, 0x8f, 0x2c, 0xab,
|
|
0xbb, 0x3c, 0xb9, 0x33, 0x01, 0x8b, 0xd8, 0x58, 0x4b, 0x7b, 0x01, 0xba, 0x6f,
|
|
0x6d, 0x51, 0x6e, 0x3a, 0xc3, 0x67, 0x42, 0x4b, 0xeb, 0x02, 0x36, 0x43, 0xd6,
|
|
0x66, 0x56, 0xca, 0x9e, 0x72, 0xcc, 0x30, 0x00, 0x00,
|
|
]);
|
|
|
|
const EXPECTED_TEXT =
|
|
'If you can read this, the Quantum decompressor is working!\n';
|
|
|
|
const ZERO_FRAME = Uint8Array.from([
|
|
0xff, 0x6d, 0xda, 0x34, 0x62, 0x1a, 0x9b, 0xa9, 0x92, 0x04, 0xd2, 0x80, 0x00,
|
|
0x20,
|
|
]);
|
|
|
|
const SECOND_ZERO_FRAME = Uint8Array.from([0x69, 0x33, 0x90, 0x00, 0x06, 0x00]);
|
|
|
|
function expectCode(action: () => unknown, code: string): void {
|
|
try {
|
|
action();
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(OnePkgError);
|
|
expect((error as OnePkgError).diagnostic.code).toBe(code);
|
|
return;
|
|
}
|
|
throw new Error(`Expected OnePkgError ${code}`);
|
|
}
|
|
|
|
describe('Quantum decoder', () => {
|
|
it('decodes the pinned compcol qtm.txt CFDATA payload', () => {
|
|
const decoder = new QuantumDecoder(18, { maxOperations: 1_000_000 });
|
|
decoder.appendCabBlock(QTM_TEXT, true);
|
|
const output = decoder.decompressNext(EXPECTED_TEXT.length, true);
|
|
expect(new TextDecoder().decode(output)).toBe(EXPECTED_TEXT);
|
|
});
|
|
|
|
it('rejects truncated streams and enforces the operation budget', () => {
|
|
expectCode(() => {
|
|
const decoder = new QuantumDecoder(18, {
|
|
maxOperations: 1_000_000,
|
|
});
|
|
decoder.appendCabBlock(QTM_TEXT.subarray(0, 4), true);
|
|
decoder.decompressNext(EXPECTED_TEXT.length, true);
|
|
}, 'quantum-unexpected-eof');
|
|
expectCode(() => {
|
|
const decoder = new QuantumDecoder(18, { maxOperations: 10 });
|
|
decoder.appendCabBlock(QTM_TEXT, true);
|
|
decoder.decompressNext(EXPECTED_TEXT.length, true);
|
|
}, 'quantum-operation-limit');
|
|
});
|
|
|
|
it('checks cancellation during a Quantum frame', () => {
|
|
const cancelled = new Error('cancelled');
|
|
const decoder = new QuantumDecoder(18, {
|
|
maxOperations: 10_000_000,
|
|
checkCancellation: () => {
|
|
throw cancelled;
|
|
},
|
|
});
|
|
decoder.appendCabBlock(ZERO_FRAME, true);
|
|
expect(() => decoder.decompressNext(32_768, true)).toThrow(cancelled);
|
|
});
|
|
|
|
it('appends the CAB sentinel and preserves state across frames', () => {
|
|
const decoder = new QuantumDecoder(18, { maxOperations: 10_000_000 });
|
|
decoder.appendCabBlock(ZERO_FRAME, false);
|
|
decoder.appendCabBlock(SECOND_ZERO_FRAME, true);
|
|
const first = decoder.decompressNext(32_768, false);
|
|
const second = decoder.decompressNext(32_768, true);
|
|
expect(first.every((value) => value === 0)).toBe(true);
|
|
expect(second.every((value) => value === 0)).toBe(true);
|
|
});
|
|
|
|
it('bounds and validates Quantum frame padding before the sentinel', () => {
|
|
const invalid = new QuantumDecoder(18, {
|
|
maxOperations: 10_000_000,
|
|
});
|
|
invalid.appendCabBlock(Uint8Array.from([...ZERO_FRAME, 0x42]), true);
|
|
expectCode(
|
|
() => invalid.decompressNext(32_768, true),
|
|
'quantum-invalid-frame-trailer'
|
|
);
|
|
|
|
const tooMuchPadding = new QuantumDecoder(18, {
|
|
maxOperations: 10_000_000,
|
|
});
|
|
tooMuchPadding.appendCabBlock(
|
|
Uint8Array.from([...ZERO_FRAME, 0, 0, 0, 0, 0]),
|
|
true
|
|
);
|
|
expectCode(
|
|
() => tooMuchPadding.decompressNext(32_768, true),
|
|
'quantum-invalid-frame-trailer'
|
|
);
|
|
});
|
|
|
|
it('rejects partial non-final frames and data after the final block', () => {
|
|
const decoder = new QuantumDecoder(18, { maxOperations: 1_000_000 });
|
|
decoder.appendCabBlock(QTM_TEXT, true);
|
|
expectCode(
|
|
() => decoder.decompressNext(EXPECTED_TEXT.length, false),
|
|
'quantum-partial-intermediate-frame'
|
|
);
|
|
decoder.decompressNext(EXPECTED_TEXT.length, true);
|
|
expectCode(
|
|
() => decoder.decompressNext(EXPECTED_TEXT.length, true),
|
|
'quantum-data-after-final-block'
|
|
);
|
|
});
|
|
});
|