can1357/oh-my-pi · error · ArchiveError
Invalid ${label} Huffman table: oversubscribed codes
Error message
Invalid ${label} Huffman table: oversubscribed codes What it means
CanonicalHuffman.build() validates a Huffman code-length table read from an LZH/LHA archive stream before constructing the canonical decoding tree. The first code at a given bit length (code = previous*2) plus the number of symbols assigned that length exceeds the total number of distinct codes possible at that length (2^length), meaning the table is mathematically inconsistent and no valid canonical Huffman code can exist. The library throws ArchiveError to reject a corrupt or hostile archive instead of building a broken tree.
Source
Thrown at packages/utils/src/ar/lzh.ts:81
static build(lengths: Uint8Array, symbolCount: number, label: string): CanonicalHuffman {
const counts = new Uint32Array(17);
let maximumLength = 0;
for (let symbol = 0; symbol < symbolCount; symbol++) {
const length = lengths[symbol]!;
if (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);
if (length !== 0) {
counts[length]++;
maximumLength = Math.max(maximumLength, length);
}
}
if (maximumLength === 0) throw new ArchiveError(`Invalid ${label} Huffman table: no symbols`);
const nextCodes = new Uint32Array(17);
let code = 0;
for (let length = 1; length <= 16; length++) {
code = (code + counts[length - 1]!) * 2;
if (code + counts[length]! > 2 ** length) {
throw new ArchiveError(`Invalid ${label} Huffman table: oversubscribed codes`);
}
nextCodes[length] = code;
}
if (nextCodes[maximumLength]! + counts[maximumLength]! !== 2 ** maximumLength) {
throw new ArchiveError(`Invalid ${label} Huffman table: incomplete codes`);
}
const tree = new CanonicalHuffman(label);
for (let symbol = 0; symbol < symbolCount; symbol++) {
const length = lengths[symbol]!;
if (length === 0) continue;
const symbolCode = nextCodes[length]!;
nextCodes[length] = symbolCode + 1;
let node = 0;
for (let bitIndex = length - 1; bitIndex >= 0; bitIndex--) {
if (tree.#symbol[node]! >= 0) {
throw new ArchiveError(`Invalid ${label} Huffman table: prefix collision`);
}View on GitHub (pinned to 9690622007)
Solutions
- Verify the archive with its checksum/unlha -t or re-extract from a known-good source
- Check that the compressed data starts at the correct offset (method id LH5/LH6/LH7 + header size), not shifted by header misparse
- If producing archives with your own encoder, recompute canonical code lengths from actual symbol frequencies and ensure Kraft sum equals 1
- Catch ArchiveError and surface 'archive is corrupted' to the user rather than retrying
Example fix
// before: trusting raw lengths read from a custom encoder
lengths = encodeFrequencies(freqs); // may be oversubscribed
// after: build a proper canonical length table
const { lengths } = packageLengths(limitedLengthHuffman(freqs, 16)); // ensures sum(counts[l]/2^l) <= 1 Defensive patterns
Strategy: try-catch
Validate before calling
// No pre-call API exists: table bytes come from the archive stream.
// Pre-validate the file before decompressing:
const stat = await Bun.file(archivePath).stat?.();
if (!stat || stat.size < 24) throw new Error('archive too small to be valid LZH'); Type guard
function isLzhMethod(method: string): boolean {
return method === '-lh0-' || method === '-lh5-' || method === '-lh6-' || method === '-lh7-';
} Try / catch
try {
const out = decompressLhStatic(compressed, originalSize);
} catch (err) {
if (err instanceof ArchiveError) {
throw new Error(`LZH archive table is corrupt: ${err.message}`);
}
throw err;
} Prevention
- Verify archive checksums/CRC before extraction
- Use the correct LH-method parser variant for the file
- Never hand-edit code-length tables; use standard limited-length Huffman construction
- Treat untrusted archives as corrupt on first ArchiveError — do not retry
When it happens
Trigger: Decompressing an LH5/LH6/LH7-format stream (decompressLhStatic / readTemporaryTree / readPositionTree) whose header-encoded code-length counts are corrupted, truncated mid-table, or hand-crafted so counts[length] overshoots 2^length for some length 1..16.
Common situations: Corrupted .lzh/.lha files (bad download, disk damage), archives concatenated or byte-shifted so the bit reader parses garbage lengths, fuzz-crafted inputs, or writing a custom LH-compressor that emits invalid length distributions.
Related errors
- Invalid ${label} Huffman table: incomplete codes
- Invalid ${label} temporary Huffman table
- Invalid ${label} Huffman table: prefix collision
- Invalid ${label} Huffman table: duplicate code
- Invalid ${this.#label} Huffman code
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e246d29197b95876.
Report an issue: GitHub.