can1357/oh-my-pi · error · ArchiveError

Invalid ${label} position Huffman table size

Error message

Invalid ${label} position Huffman table size

What it means

readPositionTree reads the count of encoded position symbols; if that count exceeds the position symbol space (positionSymbols), the table cannot be valid. This guards against corrupt bits or wrong positionBits/symbolCount parameters.

Source

Thrown at packages/utils/src/ar/lzh.ts:189

			const skipped = code === 0 ? 1 : code === 1 ? reader.read(4) + 3 : reader.read(9) + 20;
			if (index + skipped > encodedCount) throw new ArchiveError(`Invalid ${label} command Huffman table`);
			index += skipped;
		} else {
			lengths[index++] = code - 2;
		}
	}
	return CanonicalHuffman.build(lengths, symbolCount, label);
}

function readPositionTree(
	reader: MsbBitReader,
	positionBits: number,
	symbolCount: number,
	label: string,
): CanonicalHuffman {
	const encodedCount = reader.read(positionBits);
	if (encodedCount === 0) return CanonicalHuffman.single(reader.read(positionBits), symbolCount, label);
	if (encodedCount > symbolCount) throw new ArchiveError(`Invalid ${label} position Huffman table size`);
	const lengths = new Uint8Array(symbolCount);
	for (let index = 0; index < encodedCount; index++) lengths[index] = readCodeLength(reader, label);
	return CanonicalHuffman.build(lengths, symbolCount, label);
}

/** @internal Decode the static-Huffman LZSS stream shared by LZH and ARJ methods 1-3. */
export function decompressLhStatic(
	packed: Uint8Array,
	outSize: number,
	dictionarySize: number,
	positionBits: number,
	positionSymbols: number,
	label: string,
): Uint8Array {
	const reader = new MsbBitReader(packed, label);
	const output = new Uint8Array(outSize);
	let outputPosition = 0;
	let blockRemaining = 0;

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the correct positionBits/positionSymbols for the method being decoded (e.g. larger table for -lh6-/-lh7-)
  2. Re-extract or validate the archive source; check CRC
  3. Verify the bit reader starts at the true start of the compressed stream
  4. Handle ArchiveError for untrusted inputs

Example fix

// before
decompressLhStatic(packed, outSize, 5, 256, "LZH -lh5-"); // data is actually -lh7-
// after
// -lh7- uses 20-bit positions / larger symbol table
decompressLhStatic(packed, outSize, 20, 1024, "LZH -lh7-");
Defensive patterns

Strategy: validation

Validate before calling

// positionSymbols must be large enough for the method's positionBits
if (2 ** positionBits > positionSymbols) throw new Error("position table parameters inconsistent for this method");

Try / catch

try {
  const out = decompressLhStatic(packed, outSize, positionBits, positionSymbols, label);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("position Huffman")) return null;
  throw err;
}

Prevention

When it happens

Trigger: decompressLhStatic on data whose position-table symbol count field (read with positionBits bits) yields a value greater than positionSymbols — corrupt stream, or positionBits/positionSymbols mismatched with the actual method (e.g. -lh5- vs -lh7- tables).

Common situations: Decoding -lh7-/ARJ method 3 data with -lh5- parameters (positionSymbols too small); bitmisalignment after a bad command table; fuzzed inputs.

Related errors


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