feat: add bounded Quantum cabinet extraction
This commit is contained in:
121
src/onenote/onepkg/quantum/decoder.test.ts
Normal file
121
src/onenote/onepkg/quantum/decoder.test.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user