/* * 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); } }