feat: add bounded Quantum cabinet extraction

This commit is contained in:
2026-07-22 19:23:00 +02:00
parent 531612829d
commit a7b1cd7d16
20 changed files with 1099 additions and 66 deletions

View File

@@ -8,18 +8,19 @@
- Add a worker-hosted native TypeScript parser for a desktop OneNote
revision-store subset: bounded file-node and object-space traversal,
property sets, page metadata, titles, timestamps, levels, and plain text.
- Add in-memory CAB enumeration and extraction for uncompressed, MSZIP, and LZX
folders, including checksums, strict paths, explicit resource limits, and
cooperative cancellation between data blocks.
- Add in-memory CAB enumeration and extraction for uncompressed, MSZIP,
Quantum, and LZX folders, including checksums, strict paths, explicit
resource limits, and cooperative cancellation between data blocks.
- Open contained `.one` sections independently and expose package entries,
section/page navigation, partial failures, and structured diagnostics.
- Verify a Joplin `.one` golden with three pages and a Joplin LZX `0x1203`
`.onepkg` golden with seven entries, five sections, and seven pages. Also
verify rust-cab uncompressed, MSZIP, and LZX `0x1303` fixtures plus a
generated two-block MSZIP dictionary-chain fixture.
generated two-block MSZIP dictionary-chain fixture and pinned Quantum
single-/multi-frame payloads.
- Add deterministic static release packaging, SHA-256 sidecars, pinned-source
provenance, fixture licensing, and security/support documentation.
Known unsupported areas are FSSHTTP-packaged `.one`, Quantum CAB extraction,
multi-cabinet archives, `.onetoc2` hierarchy/order interpretation, visual
content, attachments, layout/format fidelity, links, and exports.
Known unsupported areas are FSSHTTP-packaged `.one`, multi-cabinet archives,
`.onetoc2` hierarchy/order interpretation, visual content, attachments,
layout/format fidelity, links, and exports.

21
LICENSES/compcol-MIT.txt Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Karpeles Lab Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -14,8 +14,8 @@ and is delivered to Toolbox Portal as an already-built, checksummed static ZIP.
- Desktop revision-store `.one` sections from the tested OneNote family. The
reader exposes section/page names, stable page IDs, created and updated
timestamps when present, page level, and readable plain text.
- Single-cabinet `.onepkg` files whose CAB folders are uncompressed, MSZIP, or
LZX compressed. Each contained desktop `.one` section is parsed
- Single-cabinet `.onepkg` files whose CAB folders are uncompressed, MSZIP,
Quantum, or LZX compressed. Each contained desktop `.one` section is parsed
independently, so one unsupported section does not hide sections that did
parse.
- Package entries, safe normalized paths, uncompressed sizes, compression
@@ -23,19 +23,20 @@ and is delivered to Toolbox Portal as an already-built, checksummed static ZIP.
The golden tests parse a real Joplin desktop `.one` file with three pages and a
real Joplin `.onepkg` with seven entries, five sections, and seven pages. LZX
`0x1203` and `0x1303`, uncompressed CAB, and one- and two-block MSZIP data are
covered by fixtures. The MSZIP fixtures are CAB-level evidence, not a claim
about a particular OneNote `.onepkg` producer.
`0x1203` and `0x1303`, uncompressed CAB, one- and two-block MSZIP data, and
single- and multi-frame Quantum data are covered by fixtures. The MSZIP and
Quantum fixtures are CAB-level evidence, not a claim about a particular
OneNote `.onepkg` producer.
This is a structural reader, not a full-fidelity OneNote renderer. Images,
tables, ink, embedded files, layout, formatting, links, and exports are not
rendered. FSSHTTP-packaged `.one`, Quantum CAB extraction, multi-cabinet
archives, and `.onetoc2` hierarchy/order interpretation are not supported. See
rendered. FSSHTTP-packaged `.one`, multi-cabinet archives, and `.onetoc2`
hierarchy/order interpretation are not supported. See
[format support](docs/FORMAT_SUPPORT.md) for the precise matrix.
There is no Rust/Wasm runtime, server, proxy, upload, telemetry, or notebook
storage. All parsing and CAB/LZX/MSZIP decompression is ordinary browser
JavaScript produced from the TypeScript source.
storage. All parsing and CAB/LZX/MSZIP/Quantum decompression is ordinary
browser JavaScript produced from the TypeScript source.
## Use

View File

@@ -23,6 +23,11 @@ adaptation of `lzxd` 0.2.5 (MIT OR Apache-2.0), pinned at
option. Adapted modules carry provenance headers and the licence texts are in
`LICENSES/rust-cab-MIT.txt` and `LICENSES/lzxd-MIT.txt`.
The browser-native Quantum decoder adapts compcol's MIT-licensed Quantum
modules, pinned at `04a6db2aa7bd487a89c559631d79d1b384139f50`. Its adapted
modules carry provenance headers and its licence is in
`LICENSES/compcol-MIT.txt`.
MSZIP raw DEFLATE decoding uses fflate 0.8.3 (MIT). Its complete installed
licence text is included in the generated runtime licence bundle.

View File

@@ -11,7 +11,7 @@
| CAB LZX `0x1303` | Supported extraction | Byte-for-byte rust-cab MIT unit fixture, two text entries |
| CAB without compression | Supported extraction | Byte-for-byte rust-cab MIT unit fixture |
| CAB MSZIP | Supported extraction | rust-cab single-block fixture plus a generated two-block dictionary-chain fixture |
| CAB Quantum | Unsupported | Metadata is recognized; extraction fails with `cab-compression-unsupported` |
| CAB Quantum | Supported extraction | Pinned compcol qtm.txt payload, generated one-folder CAB, and two-frame state/sentinel tests |
| Multi-cabinet CAB / spanning files | Unsupported | Header flags and spanning folder indexes are rejected |
| `.onetoc2` package entries | Listed only | Files are identified, but notebook hierarchy and section order are not interpreted |
| Standalone `.onetoc2` | Unsupported in the app | The picker accepts only `.one` and `.onepkg` |
@@ -30,6 +30,14 @@ and carries at most 32 KiB of history between blocks in one folder. The MSZIP
tests establish CAB extraction behavior; no redistributable MSZIP-compressed
OneNote export is currently present, so they do not establish producer-specific
`.onepkg` compatibility.
Quantum validates level/window metadata, bounds its window to 2 MiB by default,
requires full 32 KiB intermediate frames, preserves its arithmetic models and
LZ window across frames, and injects exactly one `0xFF` CAB block sentinel.
Trailer padding is limited to the documented 0..4 zero bytes; match, frame,
output, operation, and cancellation bounds are enforced. Its tests likewise
establish CAB extraction behavior only: no redistributable Quantum-compressed
OneNote export is currently present.
Trailing CAB data is ignored with a warning. Reserved CAB areas are bounded and
skipped. CAB names are decoded as strict UTF-8; unmarked valid UTF-8 receives a
warning, while invalid non-ASCII legacy-code-page names are rejected rather

View File

@@ -12,11 +12,12 @@ The desktop `.one` reader adapts selected structures from the pinned
The browser-native package path has separate MIT provenance:
| TypeScript module | Upstream path(s) | Revision and selected licence | Port scope and deviations |
| -------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/onenote/onepkg/{cabinet,checksum}.ts` | rust-cab `src/{cabinet,folder,file,ctype,checksum}.rs` | `c5839f5fdfa4c4e7cc9b22f570c79c96af0560e2`, MIT | CAB reading/checksum adapted for immutable browser bytes, strict normalized paths, checked ranges/checksums, whole-folder extraction, explicit limits, and cancellation/yield hooks; writers are not ported |
| `src/onenote/onepkg/mszip/decoder.ts` | rust-cab `src/mszip.rs`; fflate raw DEFLATE | rust-cab revision above; fflate 0.8.3, MIT | `CK` framing and persistent 32 KiB folder history follow rust-cab; raw DEFLATE uses fflate with a caller-owned bounded buffer and strict declared-size checks |
| `src/onenote/onepkg/lzx/{bit-reader,huffman,decoder}.ts` | lzxd 0.2.5 `src/{bitstream,tree,block,window,lib}.rs` | `4748e43594e3e30cff2ace3a6ad7a376c9816fdd`, MIT option of MIT OR Apache-2.0 | Directly translated core with typed arrays, CAB CFDATA boundaries, persistent folder state, strict malformed-stream errors, and operation/window/cancellation limits; delta reference data is omitted |
| TypeScript module | Upstream path(s) | Revision and selected licence | Port scope and deviations |
| ------------------------------------------------------------------------ | ------------------------------------------------------ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/onenote/onepkg/{cabinet,checksum}.ts` | rust-cab `src/{cabinet,folder,file,ctype,checksum}.rs` | `c5839f5fdfa4c4e7cc9b22f570c79c96af0560e2`, MIT | CAB reading/checksum adapted for immutable browser bytes, strict normalized paths, checked ranges/checksums, whole-folder extraction, explicit limits, and cancellation/yield hooks; writers are not ported |
| `src/onenote/onepkg/mszip/decoder.ts` | rust-cab `src/mszip.rs`; fflate raw DEFLATE | rust-cab revision above; fflate 0.8.3, MIT | `CK` framing and persistent 32 KiB folder history follow rust-cab; raw DEFLATE uses fflate with a caller-owned bounded buffer and strict declared-size checks |
| `src/onenote/onepkg/quantum/{bit-reader,budget,decoder,model,tables}.ts` | compcol `src/quantum/{bits,decoder,model,tables}.rs` | `04a6db2aa7bd487a89c559631d79d1b384139f50`, MIT | Native TypeScript arithmetic/model/LZ decoder; fixed CAB output, 2 MiB maximum window, strict match/frame/trailer checks, two-block lookahead, operation budget, and cancellation checkpoints |
| `src/onenote/onepkg/lzx/{bit-reader,huffman,decoder}.ts` | lzxd 0.2.5 `src/{bitstream,tree,block,window,lib}.rs` | `4748e43594e3e30cff2ace3a6ad7a376c9816fdd`, MIT option of MIT OR Apache-2.0 | Directly translated core with typed arrays, CAB CFDATA boundaries, persistent folder state, strict malformed-stream errors, and operation/window/cancellation limits; delta reference data is omitted |
The bounded binary readers, format detection, parser limits/error model, path
policy, worker protocol/DTOs, package-to-section boundary, and application UI
@@ -24,8 +25,8 @@ are original TypeScript implementations guided by the normative specifications
and project requirements.
Files adapted from `onenote.rs` carry an MPL-2.0 notice, SPDX identifier,
upstream path/revision, and deviations. Files adapted from rust-cab or lzxd
carry their MIT provenance, copyright notice, exact revision, and deviations.
upstream path/revision, and deviations. Files adapted from rust-cab, compcol,
or lzxd carry their MIT provenance, copyright notice, exact revision, and deviations.
Licence texts are reproduced in `LICENSES/` and included in the release ZIP.
Focused tests cover successful pinned fixtures plus malformed bounds,

View File

@@ -8,10 +8,11 @@ Version 0.1.0 records these immutable implementation revisions:
| [`toolbox-portal`](https://git.add-ideas.de/zemion/toolbox-portal/src/commit/5d2e466ad61e36a0e2be2a2d8459b1a127f53f11) | `5d2e466ad61e36a0e2be2a2d8459b1a127f53f11` | Reviewed ZIP shape, exact checksum lock, and independent-app assembly model |
| [`onenote.rs`](https://github.com/msiemens/onenote.rs/tree/5138a39a3f4e72b840932f9872fecde52fa9da60) | `5138a39a3f4e72b840932f9872fecde52fa9da60` | Selected desktop OneStore and MS-ONE structures adapted into TypeScript |
| [`rust-cab`](https://github.com/mdsteele/rust-cab/tree/c5839f5fdfa4c4e7cc9b22f570c79c96af0560e2) | `c5839f5fdfa4c4e7cc9b22f570c79c96af0560e2` | CAB structures, checksum, enumeration/extraction, MSZIP history, and unit fixtures |
| [`compcol`](https://github.com/KarpelesLab/compcol/tree/04a6db2aa7bd487a89c559631d79d1b384139f50) | `04a6db2aa7bd487a89c559631d79d1b384139f50` | Quantum bit reader, arithmetic models, LZ window, CAB sentinel behavior, and tests |
| [`lzxd` 0.2.5](https://github.com/Lonami/lzxd/tree/4748e43594e3e30cff2ace3a6ad7a376c9816fdd) | `4748e43594e3e30cff2ace3a6ad7a376c9816fdd` | LZX bitstream, Huffman, block/window, match, and E8 logic adapted into TypeScript |
The SDK and Portal revisions were inspected from the local repositories. The
three parser references were cloned and reviewed at the hashes above; a branch
four parser references were cloned and reviewed at the hashes above; a branch
name or package version alone is not used as provenance. Source-level mappings
and behavioural deviations are in [PORTING.md](PORTING.md).

View File

@@ -58,8 +58,9 @@ The CAB/package path applies these defaults:
| Total extracted entry bytes | 512 MiB |
| Folder compression ratio | 200:1 |
| One MSZIP compressed block | 32 KiB + 12 B |
| Quantum window | 2 MiB |
| LZX window | 8 MiB |
| LZX decode operations | 1,000,000,000 |
| LZX/Quantum decode operations | 1,000,000,000 |
A CFDATA block may declare at most 32 KiB of output. Counts and offsets use
checked arithmetic and bounded readers. The CAB parser verifies supplied block
@@ -71,8 +72,12 @@ MSZIP checks its `CK` framing and raw DEFLATE data, writes into a fixed caller
buffer with one overflow-detection byte, requires the exact declared output
size, and retains no more than 32 KiB of history. LZX checks its window, Huffman
trees, block sizes, match ranges, output size, and operation budget. CAB
extraction checks cancellation and yields between data blocks so the worker can
process termination. Desktop OneStore traversal checks fragment/reference
Quantum limits its window, model scans, arithmetic renormalisation, matches,
frame/output boundaries, operation count, and trailer padding. It queues at
most the current and next compressed block so a synthetic `0xFF` sentinel can
be consumed without buffering a complete folder. CAB extraction checks
cancellation during Quantum work and yields between data blocks so the worker
can process termination. Desktop OneStore traversal checks fragment/reference
cycles, nesting, object/property/reference counts, and byte ranges.
## Rendering and links

View File

@@ -26,6 +26,7 @@ export async function collectReleaseMetadataFiles(projectRoot) {
for (const licenseName of [
'AGPL-3.0.txt',
'compcol-MIT.txt',
'MPL-2.0.txt',
'lzxd-MIT.txt',
'rust-cab-MIT.txt',

View File

@@ -67,6 +67,19 @@ const MULTI_BLOCK_MSZIP_CAB = Uint8Array.from(
)
);
/*
* Generated one-folder CAB around the pinned compcol qtm.txt CFDATA payload.
* Provenance and hashes are recorded in tests/fixtures/README.md.
*/
const QUANTUM_CAB = Uint8Array.from(
Buffer.from(
'TVNDRgAAAAB8AAAAAAAAACwAAAAAAAAAAwEBAAEAAAAAAAAARAAAAAEAIhI7AAAAAAAA' +
'AAAAAAAAACAAcXRtLnR4dAD9LexLMAA7ANYGaQvLR/AsKjqPLKu7PLkzAYvYWEt7Abpv' +
'bVFuOsNnQkvrAjZD1mZWyp5yzDAAAA==',
'base64'
)
);
const LZX_CAB = binary(
'\x4d\x53\x43\x46\x00\x00\x00\x00\x97\x00\x00\x00\x00\x00\x00' +
'\x00\x2c\x00\x00\x00\x00\x00\x00\x00\x03\x01\x01\x00\x02\x00' +
@@ -226,6 +239,69 @@ describe('CAB enumeration and extraction', () => {
});
});
it('enumerates and extracts the pinned Quantum CAB payload', async () => {
expect(QUANTUM_CAB).toHaveLength(124);
expect(createHash('sha256').update(QUANTUM_CAB).digest('hex')).toBe(
'f622c5a8f5a000dfaddc5569750344f5a6e35d0de9fbd3c4f0c8720c7d94d291'
);
const listing = enumerateCabinet(QUANTUM_CAB);
expect(listing.folders[0]).toMatchObject({
dataBlockCount: 1,
compressedSize: 48,
uncompressedSize: 59,
compression: {
kind: 'quantum',
raw: 0x1222,
level: 2,
memoryBits: 18,
windowBytes: 262_144,
},
});
expect(listing.diagnostics).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ code: 'cab-compression-unsupported' }),
])
);
const extracted = await extractCabinet(QUANTUM_CAB);
expect(extracted.extractedEntries[0]!.normalizedPath).toBe('qtm.txt');
expect(new TextDecoder().decode(extracted.extractedEntries[0]!.bytes)).toBe(
'If you can read this, the Quantum decompressor is working!\n'
);
});
it('enforces Quantum type, window, and operation limits', () => {
const reservedBits = QUANTUM_CAB.slice();
new DataView(
reservedBits.buffer,
reservedBits.byteOffset,
reservedBits.byteLength
).setUint16(42, 0x3222, true);
expectCode(() => enumerateCabinet(reservedBits), 'cab-invalid-compression');
expectCode(
() =>
enumerateCabinet(QUANTUM_CAB, {
limits: { maxQuantumWindowBytes: 128 * 1024 },
}),
'cab-quantum-window-limit'
);
expectCode(
() =>
enumerateCabinet(QUANTUM_CAB, {
limits: { maxDecodeOperations: 58 },
}),
'quantum-operation-limit'
);
});
it('rejects a corrupt Quantum CFDATA payload before decoding', async () => {
const corrupted = QUANTUM_CAB.slice();
corrupted[corrupted.length - 1] = corrupted[corrupted.length - 1]! ^ 1;
await expect(extractCabinet(corrupted)).rejects.toMatchObject({
diagnostic: { code: 'cab-checksum-mismatch' },
});
});
it('retains an uncompressed LZX block across CAB chunks', () => {
const decoder = new LzxDecoder(18, { maxOperations: 100 });
const first = binary(

View File

@@ -15,6 +15,7 @@ import { fail, warning } from './errors.js';
import { LzxDecoder } from './lzx/decoder.js';
import { MsZipDecoder } from './mszip/decoder.js';
import { decodeCabFileName, normalizeCabPath } from './path.js';
import { QuantumDecoder } from './quantum/decoder.js';
import {
resolveLimits,
type CabCompressionMethod,
@@ -60,6 +61,27 @@ interface ParsedCabinet {
result: CabEnumerationResult;
}
function verifyDataBlockChecksum(bytes: Uint8Array, block: CabDataBlock): void {
if (block.checksum === 0) return;
const compressed = bytes.subarray(
block.dataOffset,
block.dataOffset + block.compressedSize
);
const actual = calculateDataBlockChecksum(
block.reserve,
compressed,
block.compressedSize,
block.uncompressedSize
);
if (actual !== block.checksum) {
fail(
'cab-checksum-mismatch',
`CAB data block checksum mismatch: expected 0x${block.checksum.toString(16).padStart(8, '0')}, got 0x${actual.toString(16).padStart(8, '0')}.`,
{ offset: block.headerOffset, structure: 'CFDATA.csum' }
);
}
}
function checkedAdd(
left: number,
right: number,
@@ -108,7 +130,13 @@ function parseCompression(
if (kind === 2) {
const level = (raw & 0x00f0) >>> 4;
const memoryBits = (raw & 0x1f00) >>> 8;
if (level < 1 || level > 7 || memoryBits < 10 || memoryBits > 21) {
if (
(raw & ~0x1ff2) !== 0 ||
level < 1 ||
level > 7 ||
memoryBits < 10 ||
memoryBits > 21
) {
fail(
'cab-invalid-compression',
`Invalid Quantum type 0x${raw.toString(16)}.`,
@@ -118,7 +146,22 @@ function parseCompression(
}
);
}
return { kind: 'quantum', raw, label: 'Quantum', level, memoryBits };
const windowBytes = 2 ** memoryBits;
if (windowBytes > limits.maxQuantumWindowBytes) {
fail(
'cab-quantum-window-limit',
`Quantum window ${windowBytes} exceeds the configured ${limits.maxQuantumWindowBytes}-byte limit.`,
{ offset, structure: 'CFFOLDER.typeCompress' }
);
}
return {
kind: 'quantum',
raw,
label: 'Quantum',
level,
memoryBits,
windowBytes,
};
}
if (kind === 3) {
if ((raw & ~0x1f0f) !== 0) {
@@ -404,6 +447,7 @@ function parseCabinet(
let totalFolderOutput = 0;
let totalLzxOutput = 0;
let totalQuantumOutput = 0;
for (const folder of folders) {
if (folder.firstDataBlockOffset > declaredSize) {
fail(
@@ -533,6 +577,14 @@ function parseCabinet(
'CFDATA.cbUncomp'
);
}
if (folder.compression.kind === 'quantum') {
totalQuantumOutput = checkedAdd(
totalQuantumOutput,
folder.uncompressedSize,
'quantum-operation-limit',
'CFDATA.cbUncomp'
);
}
}
if (totalFolderOutput > limits.maxTotalUncompressedBytes) {
fail(
@@ -548,6 +600,13 @@ function parseCabinet(
{ structure: 'CFDATA.cbUncomp' }
);
}
if (totalQuantumOutput > limits.maxDecodeOperations) {
fail(
'quantum-operation-limit',
`Quantum output exceeds the configured ${limits.maxDecodeOperations}-operation limit.`,
{ structure: 'CFDATA.cbUncomp' }
);
}
const dataRanges = folders
.filter((folder) => folder.dataBlockCount > 0)
@@ -600,18 +659,6 @@ function parseCabinet(
)
);
}
for (const folder of folders) {
if (folder.compression.kind === 'quantum') {
diagnostics.push(
warning(
'cab-compression-unsupported',
`${folder.compression.label} metadata can be listed, but this build cannot extract it.`,
{ structure: `CFFOLDER[${folder.index}].typeCompress` }
)
);
}
}
return {
bytes,
limits,
@@ -655,14 +702,6 @@ export async function extractCabinet(
const extractedEntries = [];
for (const folder of parsed.folders) {
checkCancellation(options.cancellation);
if (folder.compression.kind === 'quantum') {
fail(
'cab-compression-unsupported',
`${folder.compression.label} extraction is not implemented.`,
{ structure: `CFFOLDER[${folder.index}].typeCompress` }
);
}
const folderOutput = new Uint8Array(folder.uncompressedSize);
const decoder =
folder.compression.kind === 'lzx'
@@ -673,34 +712,50 @@ export async function extractCabinet(
: undefined;
const msZipDecoder =
folder.compression.kind === 'mszip' ? new MsZipDecoder() : undefined;
const quantumDecoder =
folder.compression.kind === 'quantum'
? new QuantumDecoder(folder.compression.memoryBits, {
maxOperations: parsed.limits.maxDecodeOperations,
checkCancellation: () => checkCancellation(options.cancellation),
})
: undefined;
let quantumQueuedBlock = -1;
let outputOffset = 0;
for (const block of folder.blocks) {
for (const [blockIndex, block] of folder.blocks.entries()) {
checkCancellation(options.cancellation);
if (quantumDecoder !== undefined) {
const lookahead = Math.min(blockIndex + 1, folder.blocks.length - 1);
while (quantumQueuedBlock < lookahead) {
quantumQueuedBlock += 1;
const queued = folder.blocks[quantumQueuedBlock]!;
verifyDataBlockChecksum(parsed.bytes, queued);
quantumDecoder.appendCabBlock(
parsed.bytes.subarray(
queued.dataOffset,
queued.dataOffset + queued.compressedSize
),
quantumQueuedBlock === folder.blocks.length - 1
);
}
}
const compressed = parsed.bytes.subarray(
block.dataOffset,
block.dataOffset + block.compressedSize
);
if (block.checksum !== 0) {
const actual = calculateDataBlockChecksum(
block.reserve,
compressed,
block.compressedSize,
block.uncompressedSize
);
if (actual !== block.checksum) {
fail(
'cab-checksum-mismatch',
`CAB data block checksum mismatch: expected 0x${block.checksum.toString(16).padStart(8, '0')}, got 0x${actual.toString(16).padStart(8, '0')}.`,
{ offset: block.headerOffset, structure: 'CFDATA.csum' }
);
}
if (quantumDecoder === undefined) {
verifyDataBlockChecksum(parsed.bytes, block);
}
const output =
decoder !== undefined
? decoder.decompressNext(compressed, block.uncompressedSize)
: msZipDecoder !== undefined
? msZipDecoder.decompressNext(compressed, block.uncompressedSize)
: compressed;
: quantumDecoder !== undefined
? quantumDecoder.decompressNext(
block.uncompressedSize,
blockIndex === folder.blocks.length - 1
)
: compressed;
if (output.length !== block.uncompressedSize) {
fail(
'cab-output-size-mismatch',

View File

@@ -0,0 +1,154 @@
/*
* SPDX-License-Identifier: MIT
*
* TypeScript port of compcol `src/quantum/bits.rs` at commit
* 04a6db2aa7bd487a89c559631d79d1b384139f50.
* Copyright (c) 2026 Karpeles Lab Inc., MIT License.
* Modified for a synchronous, bounded CAB-block decoder.
*/
import { fail } from '../errors.js';
import { QuantumBudget } from './budget.js';
const BIT_BUFFER_WIDTH = 32;
export class QuantumBitReader {
private bitBuffer = 0;
private bitsLeft = 0;
private byteOffset = 0;
private endOfInput = false;
private eofPaddingUsed = false;
constructor(private readonly budget: QuantumBudget) {}
get offset(): number {
return this.byteOffset;
}
get bufferedBitCount(): number {
return this.bitsLeft;
}
setEndOfInput(): void {
this.endOfInput = true;
}
ensureBits(bitCount: number, input: Uint8Array): void {
if (!Number.isInteger(bitCount) || bitCount < 1 || bitCount > 16) {
fail('quantum-invalid-bit-count', 'Invalid Quantum bit-read width.', {
structure: 'Quantum bitstream',
});
}
while (this.bitsLeft < bitCount) this.readPair(input);
}
peekBits(bitCount: number): number {
if (
!Number.isInteger(bitCount) ||
bitCount < 1 ||
bitCount > BIT_BUFFER_WIDTH ||
bitCount > this.bitsLeft
) {
fail('quantum-invalid-bit-state', 'Invalid Quantum bit-buffer access.', {
structure: 'Quantum bitstream',
});
}
return bitCount === BIT_BUFFER_WIDTH
? this.bitBuffer
: this.bitBuffer >>> (BIT_BUFFER_WIDTH - bitCount);
}
removeBits(bitCount: number): void {
if (
!Number.isInteger(bitCount) ||
bitCount < 0 ||
bitCount > this.bitsLeft
) {
fail('quantum-invalid-bit-state', 'Invalid Quantum bit-buffer advance.', {
structure: 'Quantum bitstream',
});
}
this.bitBuffer =
bitCount === BIT_BUFFER_WIDTH ? 0 : (this.bitBuffer << bitCount) >>> 0;
this.bitsLeft -= bitCount;
}
readBits(bitCount: number, input: Uint8Array): number {
this.ensureBits(bitCount, input);
const value = this.peekBits(bitCount);
this.removeBits(bitCount);
return value;
}
readManyBits(bitCount: number, input: Uint8Array): number {
if (!Number.isInteger(bitCount) || bitCount < 0 || bitCount > 32) {
fail('quantum-invalid-bit-count', 'Invalid Quantum bit-read width.', {
structure: 'Quantum bitstream',
});
}
let needed = bitCount;
let value = 0;
while (needed > 0) {
if (this.bitsLeft <= BIT_BUFFER_WIDTH - 16) this.readPair(input);
const run = Math.min(this.bitsLeft, needed);
value = (value * 2 ** run + this.peekBits(run)) >>> 0;
this.removeBits(run);
needed -= run;
this.budget.consume();
}
return value;
}
alignToByte(): void {
this.removeBits(this.bitsLeft & 7);
}
rebase(consumedBytes: number): void {
if (consumedBytes < 0 || consumedBytes > this.byteOffset) {
fail('quantum-invalid-bit-state', 'Invalid Quantum input rebase.', {
structure: 'Quantum bitstream',
});
}
this.byteOffset -= consumedBytes;
}
private readPair(input: Uint8Array): void {
if (this.bitsLeft > BIT_BUFFER_WIDTH - 16) {
fail('quantum-invalid-bit-state', 'Quantum bit buffer overflow.', {
offset: this.byteOffset,
structure: 'Quantum bitstream',
});
}
if (this.byteOffset + 2 > input.length) {
if (this.endOfInput && !this.eofPaddingUsed) {
// libmspack supplies two zero bytes once at folder EOF. Preserve a
// final odd byte (normally the synthetic CAB sentinel) as the high
// byte of that refill instead of discarding it.
const remainingByte =
this.byteOffset < input.length ? input[this.byteOffset++]! : 0;
this.bitBuffer =
(this.bitBuffer |
(remainingByte << (BIT_BUFFER_WIDTH - 16 - this.bitsLeft))) >>>
0;
this.eofPaddingUsed = true;
this.bitsLeft += 16;
this.budget.consume();
return;
}
fail(
'quantum-unexpected-eof',
'Quantum stream ended while reading compressed bits.',
{ offset: this.byteOffset, structure: 'Quantum bitstream' }
);
}
const combined =
(input[this.byteOffset]! << 8) | input[this.byteOffset + 1]!;
this.byteOffset += 2;
this.bitBuffer =
(this.bitBuffer |
(combined << (BIT_BUFFER_WIDTH - 16 - this.bitsLeft))) >>>
0;
this.bitsLeft += 16;
this.budget.consume();
}
}

View File

@@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: MIT
*
* Quantum decoder support adapted from compcol `src/quantum/*` at commit
* 04a6db2aa7bd487a89c559631d79d1b384139f50.
* Copyright (c) 2026 Karpeles Lab Inc., MIT License.
*/
import { fail } from '../errors.js';
export interface QuantumDecoderOptions {
maxOperations: number;
checkCancellation?: () => void;
}
export class QuantumBudget {
private operations = 0;
private nextCancellationCheck = 4_096;
constructor(private readonly options: QuantumDecoderOptions) {}
consume(count = 1): void {
if (!Number.isSafeInteger(count) || count < 0) {
fail('quantum-operation-overflow', 'Invalid Quantum operation count.', {
structure: 'Quantum stream',
});
}
this.operations += count;
if (
!Number.isSafeInteger(this.operations) ||
this.operations > this.options.maxOperations
) {
fail(
'quantum-operation-limit',
`Quantum decoding exceeded the configured ${this.options.maxOperations}-operation limit.`,
{ structure: 'Quantum stream' }
);
}
if (this.operations >= this.nextCancellationCheck) {
this.options.checkCancellation?.();
this.nextCancellationCheck = this.operations + 4_096;
}
}
}

View 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'
);
});
});

View File

@@ -0,0 +1,296 @@
/*
* SPDX-License-Identifier: MIT
*
* TypeScript port of compcol `src/quantum/decoder.rs` at commit
* 04a6db2aa7bd487a89c559631d79d1b384139f50.
* Copyright (c) 2026 Karpeles Lab Inc., MIT License.
*
* Modified for synchronous CAB CFDATA chunks, fixed output buffers, explicit
* operation/cancellation budgets, strict frame/output boundaries, and one
* synthetic 0xFF trailer byte per CAB block as required by the reference.
*/
import { fail } from '../errors.js';
import { QuantumBitReader } from './bit-reader.js';
import { QuantumBudget, type QuantumDecoderOptions } from './budget.js';
import { QuantumArithmeticDecoder, QuantumModel } from './model.js';
import {
EXTRA_BITS,
LENGTH_BASE,
LENGTH_EXTRA,
POSITION_BASE,
} from './tables.js';
const FRAME_SIZE = 32 * 1024;
const MIN_WINDOW_BITS = 10;
const MAX_WINDOW_BITS = 21;
const MAX_TRAILER_ZERO_BYTES = 4;
const CAB_BLOCK_SENTINEL = 0xff;
export class QuantumDecoder {
private readonly window: Uint8Array;
private readonly windowMask: number;
private readonly budget: QuantumBudget;
private readonly bitReader: QuantumBitReader;
private readonly arithmetic: QuantumArithmeticDecoder;
private input = new Uint8Array(0);
private windowPosition = 0;
private headerRead = false;
private frameRemaining = FRAME_SIZE;
private finished = false;
private inputFinished = false;
private readonly model0: QuantumModel;
private readonly model1: QuantumModel;
private readonly model2: QuantumModel;
private readonly model3: QuantumModel;
private readonly model4: QuantumModel;
private readonly model5: QuantumModel;
private readonly model6: QuantumModel;
private readonly model6Length: QuantumModel;
private readonly model7: QuantumModel;
constructor(windowBits: number, options: QuantumDecoderOptions) {
if (
!Number.isInteger(windowBits) ||
windowBits < MIN_WINDOW_BITS ||
windowBits > MAX_WINDOW_BITS
) {
fail(
'quantum-invalid-window',
`Quantum window size 2^${windowBits} is outside 2^${MIN_WINDOW_BITS}..2^${MAX_WINDOW_BITS}.`,
{ structure: 'CFFOLDER.typeCompress' }
);
}
const windowSize = 2 ** windowBits;
this.window = new Uint8Array(windowSize);
this.windowMask = windowSize - 1;
this.budget = new QuantumBudget(options);
this.bitReader = new QuantumBitReader(this.budget);
this.arithmetic = new QuantumArithmeticDecoder(this.budget);
const positionModelSize = windowBits * 2;
this.model0 = new QuantumModel(0, 64, this.budget);
this.model1 = new QuantumModel(64, 64, this.budget);
this.model2 = new QuantumModel(128, 64, this.budget);
this.model3 = new QuantumModel(192, 64, this.budget);
this.model4 = new QuantumModel(
0,
Math.min(positionModelSize, 24),
this.budget
);
this.model5 = new QuantumModel(
0,
Math.min(positionModelSize, 36),
this.budget
);
this.model6 = new QuantumModel(0, positionModelSize, this.budget);
this.model6Length = new QuantumModel(0, 27, this.budget);
this.model7 = new QuantumModel(0, 7, this.budget);
}
appendCabBlock(compressed: Uint8Array, isLastBlock: boolean): void {
if (this.inputFinished) {
fail(
'quantum-data-after-final-block',
'Quantum decoder received data after the final CAB block.',
{ structure: 'Quantum stream' }
);
}
this.appendBlockBytes(compressed);
if (isLastBlock) {
this.inputFinished = true;
this.bitReader.setEndOfInput();
}
}
decompressNext(outputLength: number, isLastBlock: boolean): Uint8Array {
if (this.finished) {
fail(
'quantum-data-after-final-block',
'Quantum decoder received data after the final CAB block.',
{ structure: 'Quantum stream' }
);
}
if (
!Number.isInteger(outputLength) ||
outputLength <= 0 ||
outputLength > FRAME_SIZE
) {
fail(
'quantum-invalid-chunk-size',
`Quantum CAB chunk output size ${outputLength} is outside 1..${FRAME_SIZE}.`,
{ structure: 'CFDATA.cbUncomp' }
);
}
if (!isLastBlock && outputLength !== FRAME_SIZE) {
fail(
'quantum-partial-intermediate-frame',
'A non-final Quantum CAB block must produce exactly 32 KiB.',
{ structure: 'CFDATA.cbUncomp' }
);
}
const output = new Uint8Array(outputLength);
let outputOffset = 0;
while (outputOffset < output.length) {
this.budget.consume();
if (this.frameRemaining === 0) this.completeFrameTrailer();
if (!this.headerRead) {
this.arithmetic.initializeFrame(this.bitReader, this.input);
this.headerRead = true;
}
outputOffset = this.decodePacket(output, outputOffset);
}
// CAB blocks before the final partial block are complete Quantum frames.
// Consume their alignment and synthetic sentinel now, matching qtmd.c,
// so a malformed final frame cannot evade trailer validation.
if (this.frameRemaining === 0) this.completeFrameTrailer();
this.compactInput();
if (isLastBlock) this.finished = true;
return output;
}
private decodePacket(output: Uint8Array, outputOffset: number): number {
const selector = this.arithmetic.getSymbol(
this.model7,
this.bitReader,
this.input
);
if (selector < 4) {
const model = [this.model0, this.model1, this.model2, this.model3][
selector
]!;
const symbol = this.arithmetic.getSymbol(
model,
this.bitReader,
this.input
);
this.window[this.windowPosition & this.windowMask] = symbol & 0xff;
output[outputOffset] = symbol & 0xff;
this.windowPosition += 1;
this.frameRemaining -= 1;
this.budget.consume();
return outputOffset + 1;
}
let matchLength: number;
let matchOffset: number;
if (selector === 4 || selector === 5) {
const model = selector === 4 ? this.model4 : this.model5;
const slot = this.arithmetic.getSymbol(model, this.bitReader, this.input);
matchOffset = this.readPosition(slot);
matchLength = selector === 4 ? 3 : 4;
} else if (selector === 6) {
const lengthSlot = this.arithmetic.getSymbol(
this.model6Length,
this.bitReader,
this.input
);
if (lengthSlot >= LENGTH_EXTRA.length) {
fail('quantum-invalid-length-slot', 'Invalid Quantum length slot.', {
structure: 'Quantum match',
});
}
const lengthExtraBits = LENGTH_EXTRA[lengthSlot]!;
const lengthExtra = this.bitReader.readManyBits(
lengthExtraBits,
this.input
);
matchLength = LENGTH_BASE[lengthSlot]! + lengthExtra + 5;
const positionSlot = this.arithmetic.getSymbol(
this.model6,
this.bitReader,
this.input
);
matchOffset = this.readPosition(positionSlot);
} else {
fail('quantum-invalid-selector', 'Invalid Quantum packet selector.', {
structure: 'Quantum packet',
});
}
if (matchOffset <= 0 || matchOffset > this.window.length) {
fail(
'quantum-invalid-match-offset',
`Quantum match offset ${matchOffset} exceeds its ${this.window.length}-byte window.`,
{ structure: 'Quantum match' }
);
}
if (matchLength > this.frameRemaining) {
fail(
'quantum-frame-overrun',
'Quantum match crosses its 32 KiB frame boundary.',
{ structure: 'Quantum match' }
);
}
if (matchLength > output.length - outputOffset) {
fail(
'quantum-output-overrun',
'Quantum match exceeds the CAB block output declaration.',
{ structure: 'CFDATA.cbUncomp' }
);
}
for (let index = 0; index < matchLength; index += 1) {
const source =
(this.windowPosition - matchOffset + this.window.length) &
this.windowMask;
const value = this.window[source]!;
this.window[this.windowPosition & this.windowMask] = value;
output[outputOffset + index] = value;
this.windowPosition += 1;
}
this.frameRemaining -= matchLength;
this.budget.consume(matchLength);
return outputOffset + matchLength;
}
private readPosition(slot: number): number {
if (slot >= EXTRA_BITS.length) {
fail('quantum-invalid-position-slot', 'Invalid Quantum position slot.', {
structure: 'Quantum match',
});
}
const extra = this.bitReader.readManyBits(EXTRA_BITS[slot]!, this.input);
return POSITION_BASE[slot]! + extra + 1;
}
private completeFrameTrailer(): void {
this.bitReader.alignToByte();
let zeroBytes = 0;
while (true) {
const value = this.bitReader.readBits(8, this.input);
if (value === CAB_BLOCK_SENTINEL) break;
if (value !== 0 || zeroBytes >= MAX_TRAILER_ZERO_BYTES) {
fail(
'quantum-invalid-frame-trailer',
'Quantum frame trailer is not zero padding followed by 0xFF.',
{ structure: 'Quantum frame trailer' }
);
}
zeroBytes += 1;
this.budget.consume();
}
this.headerRead = false;
this.frameRemaining = FRAME_SIZE;
}
private appendBlockBytes(compressed: Uint8Array): void {
this.compactInput();
const appended = new Uint8Array(this.input.length + compressed.length + 1);
appended.set(this.input);
appended.set(compressed, this.input.length);
appended[appended.length - 1] = CAB_BLOCK_SENTINEL;
this.input = appended;
}
private compactInput(): void {
const consumed = this.bitReader.offset;
if (consumed === 0) return;
this.input = this.input.slice(consumed);
this.bitReader.rebase(consumed);
}
}

View File

@@ -0,0 +1,168 @@
/*
* SPDX-License-Identifier: MIT
*
* TypeScript port of compcol `src/quantum/model.rs` at commit
* 04a6db2aa7bd487a89c559631d79d1b384139f50.
* Copyright (c) 2026 Karpeles Lab Inc., MIT License.
* Modified to account for decoder work and report structured CAB errors.
*/
import { fail } from '../errors.js';
import { QuantumBitReader } from './bit-reader.js';
import { QuantumBudget } from './budget.js';
export class QuantumModel {
shiftsLeft = 4;
readonly symbols = new Uint16Array(65);
readonly cumulativeFrequencies = new Uint16Array(65);
constructor(
start: number,
readonly entries: number,
private readonly budget: QuantumBudget
) {
if (!Number.isInteger(entries) || entries < 1 || entries > 64) {
fail('quantum-invalid-model', 'Quantum model size is out of range.', {
structure: 'Quantum probability model',
});
}
for (let index = 0; index <= entries; index += 1) {
this.symbols[index] = start + index;
this.cumulativeFrequencies[index] = entries - index;
}
}
update(): void {
this.shiftsLeft -= 1;
if (this.shiftsLeft !== 0) {
for (let index = this.entries - 1; index >= 0; index -= 1) {
let frequency = this.cumulativeFrequencies[index]! >>> 1;
if (frequency <= this.cumulativeFrequencies[index + 1]!) {
frequency = this.cumulativeFrequencies[index + 1]! + 1;
}
this.cumulativeFrequencies[index] = frequency;
this.budget.consume();
}
return;
}
this.shiftsLeft = 50;
for (let index = 0; index < this.entries; index += 1) {
this.cumulativeFrequencies[index] =
(this.cumulativeFrequencies[index]! -
this.cumulativeFrequencies[index + 1]! +
1) >>>
1;
this.budget.consume();
}
// Preserve compcol/libmspack's selection-sort tie behavior exactly.
for (let left = 0; left < this.entries - 1; left += 1) {
for (let right = left + 1; right < this.entries; right += 1) {
if (
this.cumulativeFrequencies[left]! < this.cumulativeFrequencies[right]!
) {
const frequency = this.cumulativeFrequencies[left]!;
this.cumulativeFrequencies[left] = this.cumulativeFrequencies[right]!;
this.cumulativeFrequencies[right] = frequency;
const symbol = this.symbols[left]!;
this.symbols[left] = this.symbols[right]!;
this.symbols[right] = symbol;
}
this.budget.consume();
}
}
for (let index = this.entries - 1; index >= 0; index -= 1) {
this.cumulativeFrequencies[index] =
this.cumulativeFrequencies[index]! +
this.cumulativeFrequencies[index + 1]!;
this.budget.consume();
}
}
}
export class QuantumArithmeticDecoder {
private high = 0;
private low = 0;
private current = 0;
constructor(private readonly budget: QuantumBudget) {}
initializeFrame(reader: QuantumBitReader, input: Uint8Array): void {
this.high = 0xffff;
this.low = 0;
this.current = reader.readBits(16, input);
}
getSymbol(
model: QuantumModel,
reader: QuantumBitReader,
input: Uint8Array
): number {
const high = this.high >>> 0;
const low = this.low >>> 0;
const current = this.current >>> 0;
const range = (((high - low) >>> 0) & 0xffff) + 1;
const total = model.cumulativeFrequencies[0]!;
if (total === 0) {
fail('quantum-invalid-model', 'Quantum probability model is exhausted.', {
structure: 'Quantum probability model',
});
}
const scaledCurrent = (current - low + 1) >>> 0;
const numerator = (Math.imul(scaledCurrent, total) - 1) >>> 0;
const symbolFrequency = Math.floor(numerator / range) & 0xffff;
let index = 1;
while (
index < model.entries &&
model.cumulativeFrequencies[index]! > symbolFrequency
) {
index += 1;
this.budget.consume();
}
const symbol = model.symbols[index - 1]!;
const range2 = (high - low + 1) >>> 0;
const frequencyLow = model.cumulativeFrequencies[index - 1]!;
const frequencyHigh = model.cumulativeFrequencies[index]!;
let nextHigh =
(low + Math.floor((Math.imul(frequencyLow, range2) >>> 0) / total) - 1) &
0xffff;
let nextLow =
(low + Math.floor((Math.imul(frequencyHigh, range2) >>> 0) / total)) &
0xffff;
for (let update = index - 1; update >= 0; update -= 1) {
model.cumulativeFrequencies[update] =
(model.cumulativeFrequencies[update]! + 8) & 0xffff;
this.budget.consume();
}
if (model.cumulativeFrequencies[0]! > 3_800) model.update();
let nextCurrent = current & 0xffff;
while (true) {
if ((nextLow & 0x8000) !== (nextHigh & 0x8000)) {
if ((nextLow & 0x4000) !== 0 && (nextHigh & 0x4000) === 0) {
nextCurrent ^= 0x4000;
nextLow &= 0x3fff;
nextHigh |= 0x4000;
} else {
break;
}
}
nextLow = (nextLow << 1) & 0xffff;
nextHigh = ((nextHigh << 1) | 1) & 0xffff;
reader.ensureBits(1, input);
const bit = reader.peekBits(1);
reader.removeBits(1);
nextCurrent = ((nextCurrent << 1) | bit) & 0xffff;
this.budget.consume();
}
this.high = nextHigh;
this.low = nextLow;
this.current = nextCurrent;
this.budget.consume();
return symbol;
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: MIT
*
* Direct TypeScript translation of compcol `src/quantum/tables.rs` at commit
* 04a6db2aa7bd487a89c559631d79d1b384139f50.
* Copyright (c) 2026 Karpeles Lab Inc., MIT License.
*/
export const POSITION_BASE = Uint32Array.from([
0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768,
1_024, 1_536, 2_048, 3_072, 4_096, 6_144, 8_192, 12_288, 16_384, 24_576,
32_768, 49_152, 65_536, 98_304, 131_072, 196_608, 262_144, 393_216, 524_288,
786_432, 1_048_576, 1_572_864,
]);
export const EXTRA_BITS = Uint8Array.from([
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11,
11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19,
]);
export const LENGTH_BASE = Uint8Array.from([
0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 18, 22, 26, 30, 38, 46, 54, 62, 78, 94,
110, 126, 158, 190, 222, 254,
]);
export const LENGTH_EXTRA = Uint8Array.from([
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
0,
]);

View File

@@ -9,6 +9,7 @@ export type CabCompressionMethod =
label: 'Quantum';
level: number;
memoryBits: number;
windowBytes: number;
}
| {
kind: 'lzx';
@@ -75,6 +76,7 @@ export interface OnePkgLimits {
maxExtractedBytes: number;
/** Maximum per-folder uncompressed CFDATA bytes / compressed payload bytes. */
maxCompressionRatio: number;
maxQuantumWindowBytes: number;
maxLzxWindowBytes: number;
maxDecodeOperations: number;
}
@@ -92,6 +94,7 @@ export const DEFAULT_ONEPKG_LIMITS: Readonly<OnePkgLimits> = {
maxTotalUncompressedBytes: 512 * 1024 * 1024,
maxExtractedBytes: 512 * 1024 * 1024,
maxCompressionRatio: 200,
maxQuantumWindowBytes: 2 * 1024 * 1024,
maxLzxWindowBytes: 8 * 1024 * 1024,
maxDecodeOperations: 1_000_000_000,
};

View File

@@ -96,6 +96,43 @@ so decoding it without folder history fails.
This fixture establishes block-history behavior only. It is not a OneNote
export and is not evidence of `.onepkg` producer compatibility.
## Inline compcol Quantum fixtures
`src/onenote/onepkg/quantum/decoder.test.ts` copies two small Quantum payloads
from compcol
[`tests/quantum.rs`](https://github.com/KarpelesLab/compcol/blob/04a6db2aa7bd487a89c559631d79d1b384139f50/tests/quantum.rs)
at commit `04a6db2aa7bd487a89c559631d79d1b384139f50`. The 48-byte
`qtm.txt` payload decodes with an 18-bit window to the 59-byte ASCII string
`If you can read this, the Quantum decompressor is working!` followed by a
line feed. The first two compressed blocks of compcol's multi-frame zero-run
fixture are 14 and 6 bytes and decode to two 32,768-byte zero frames. Tests
exercise arithmetic-model and LZ-window continuity, per-block synthetic
sentinels, malformed trailers, truncation, cancellation, and operation limits.
The immediate reviewed compcol source is MIT-licensed; its exact licence is in
`LICENSES/compcol-MIT.txt`. The compcol comments record that the payloads were
originally extracted from libmspack CAB test files. This repository copies only
the small arrays published in the pinned compcol test source, not those CAB
files.
`src/onenote/onepkg/cabinet.test.ts` wraps the 48-byte payload in a generated,
single-folder CAB so the actual CAB enumeration, checksum, and extraction path
is covered:
- CAB compression type: `0x1222` (Quantum level 2, 18-bit/256 KiB window).
- CFDATA sizes/checksum: 48 compressed bytes, 59 output bytes, checksum
`0x4bec2dfd`.
- CAB size: 124 bytes.
- CAB SHA-256:
`f622c5a8f5a000dfaddc5569750344f5a6e35d0de9fbd3c4f0c8720c7d94d291`.
- Wrapper/header/filename licence: original test data under this project's
MPL-2.0 licence; embedded payload licence: compcol MIT as above.
The frozen CAB contains only the compressed payload; the decoder appends the
required `0xFF` byte after each CFDATA block in memory. These are CAB-level
codec fixtures, not OneNote exports, and do not establish producer-specific
`.onepkg` compatibility.
## Distribution policy
The decoded fixture bytes are used only by tests. Vite does not copy them into

View File

@@ -74,6 +74,7 @@ describe('release archive', () => {
expect.arrayContaining([
'LICENSE',
'LICENSES/AGPL-3.0.txt',
'LICENSES/compcol-MIT.txt',
'LICENSES/MPL-2.0.txt',
'LICENSES/THIRD-PARTY-LICENSES.txt',
'LICENSES/lzxd-MIT.txt',
@@ -94,6 +95,11 @@ describe('release archive', () => {
expect(
createHash('sha256').update(files['LICENSES/lzxd-MIT.txt']!).digest('hex')
).toBe('c7a1dcbf839b1dfb72f24e1de12ab55f789db2c61d894ca9aa8b5f595490d9ca');
expect(
createHash('sha256')
.update(files['LICENSES/compcol-MIT.txt']!)
.digest('hex')
).toBe('a7009ce74b33a2afcf9ab38007bf3c8f5020d9b816dc5fc7296bf17e6d7dc796');
expect(
files['LICENSES/THIRD-PARTY-LICENSES.txt']!.toString('utf8')
).toContain('fflate@0.8.3');