can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: oversubscribed LZX Huffman tree

Error message

Invalid CAB archive: oversubscribed LZX Huffman tree

What it means

Thrown by the LzxHuffmanTable constructor when the set of code lengths is not a valid (under-subscribed or exactly complete) Huffman code: at some bit length, the running code space exceeds 2^length, meaning the described tree assigns more codes than exist at that depth. This is a Kraft-inequality violation — the tree cannot be decoded unambiguously.

Source

Thrown at packages/utils/src/ar/codecs/lzx.ts:88

		let symbolCount = 0;
		for (const length of lengths) {
			if (length > 16) throw new ArchiveError("Invalid CAB archive: invalid LZX Huffman code length");
			if (length !== 0) {
				this.#counts[length]++;
				symbolCount++;
			}
		}
		this.empty = symbolCount === 0;
		if (this.empty && !allowEmpty) {
			throw new ArchiveError("Invalid CAB archive: empty LZX Huffman tree");
		}

		let code = 0;
		let symbolOffset = 0;
		for (let length = 1; length <= 16; length++) {
			code = (code + this.#counts[length - 1]!) * 2;
			if (code + this.#counts[length]! > 2 ** length) {
				throw new ArchiveError("Invalid CAB archive: oversubscribed LZX Huffman tree");
			}
			this.#firstCodes[length] = code;
			this.#firstSymbols[length] = symbolOffset;
			symbolOffset += this.#counts[length]!;
		}

		this.#symbols = new Uint16Array(symbolCount);
		const next = this.#firstSymbols.slice();
		for (let symbol = 0; symbol < lengths.byteLength; symbol++) {
			const length = lengths[symbol]!;
			if (length !== 0) this.#symbols[next[length]!] = symbol;
			next[length]!++;
		}
	}

	decode(reader: LzxBitReader): number {
		if (this.empty) throw new ArchiveError("Invalid CAB archive: LZX stream uses an empty Huffman tree");
		let code = 0;

View on GitHub (pinned to 9690622007)

Solutions

  1. Validate the CAB file and re-obtain a known-good copy.
  2. Recompress the archive with a standard CAB tool so trees satisfy the Kraft inequality.
  3. If building trees programmatically, compute lengths with a real Huffman algorithm (or canonical lengths) instead of hand-picked values.
  4. Confirm the bit reader offset is correct; desync earlier in the stream produces nonsensical lengths.

Example fix

// before
new LzxHuffmanTable(Uint8Array.from([1, 1])) // two length-1 codes: oversubscribed
// after
new LzxHuffmanTable(Uint8Array.from([1, 2, 2])) // valid canonical code
Defensive patterns

Strategy: try-catch

Validate before calling

function isKraftValid(lengths: Uint8Array): boolean {
  let sum = 0
  for (const l of lengths) if (l > 0) sum += 2 ** -l
  return sum <= 1 + 1e-9
}
// call isKraftValid(lengths) before constructing a table yourself

Type guard

function isKraftValid(lengths: Uint8Array): boolean {
  let sum = 0
  for (const l of lengths) if (l > 0) sum += 2 ** -l
  return sum <= 1 + 1e-9
}

Try / catch

try {
  decoder.decompressFrame(bytes, size)
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('oversubscribed')) {
    throw new Error('CAB archive is corrupt: invalid Huffman code definition')
  }
  throw err
}

Prevention

When it happens

Trigger: Decompressing a CAB LZX block whose main/length/aligned/pretree code lengths sum (weighted by 2^-length) to more than 1, e.g. two symbols with length 1, or lengths that over-fill a level after earlier levels are counted.

Common situations: Corrupted CAB files, archives written by broken compressors, decoding a non-LZX stream as LZX so garbage bits are interpreted as tree lengths.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/8208a919efdc767e. Report an issue: GitHub.