can1357/oh-my-pi · error · ArchiveError
Invalid ${label} Huffman table: duplicate code
Error message
Invalid ${label} Huffman table: duplicate code What it means
build() rejects a symbol whose canonical code resolves to a node that is already a leaf (symbol assigned) or already has children — the same code appears twice in the table. Duplicate codes make decoding ambiguous, so the library throws ArchiveError before any data is decoded.
Source
Thrown at packages/utils/src/ar/lzh.ts:113
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`);
}
const bit = (symbolCode >>> bitIndex) & 1;
let child = bit === 0 ? tree.#zero[node]! : tree.#one[node]!;
if (child < 0) {
child = tree.#symbol.length;
tree.#zero.push(-1);
tree.#one.push(-1);
tree.#symbol.push(-1);
if (bit === 0) tree.#zero[node] = child;
else tree.#one[node] = child;
}
node = child;
}
if (tree.#symbol[node]! >= 0 || tree.#zero[node]! >= 0 || tree.#one[node]! >= 0) {
throw new ArchiveError(`Invalid ${label} Huffman table: duplicate code`);
}
tree.#symbol[node] = symbol;
}
return tree;
}
decode(reader: MsbBitReader): number {
let node = 0;
for (let depth = 0; depth <= 16; depth++) {
const symbol = this.#symbol[node]!;
if (symbol >= 0) return symbol;
node = reader.read(1) === 0 ? this.#zero[node]! : this.#one[node]!;
if (node < 0) throw new ArchiveError(`Invalid ${this.#label} Huffman code`);
}
throw new ArchiveError(`Invalid ${this.#label} Huffman code: excessive depth`);
}
}
View on GitHub (pinned to 9690622007)
Solutions
- Re-extract the archive from a trusted source and verify checksums
- Confirm the table region offset is correct (no header-size misparse shifting the bitstream)
- If implementing a compressor, derive lengths via standard package-merge/limit-length Huffman rather than ad-hoc assignment
- Catch ArchiveError and classify the file as corrupt
Defensive patterns
Strategy: try-catch
Try / catch
try {
const out = decompressLhStatic(data, size);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes('duplicate code')) {
// classify and quarantine the archive
return null;
}
throw err;
} Prevention
- Check archive CRCs before extraction
- Avoid custom/ad-hoc compressors that bypass canonical length assignment
- Keep the table read at the exact stream offset (no header misparse)
- Fuzz-test your encoder's table output against this decoder
When it happens
Trigger: Decompressing an LZH stream whose code-length array contains repeated identical nonzero-length entries in a way canonical assignment maps two symbols to one code — typically from corruption of the lengths block or an encoder bug writing lengths without deduplication by canonical construction.
Common situations: Corrupt or truncated .lzh files, buggy third-party LH compressors, fuzzed inputs targeting the table parser.
Related errors
- Invalid ${label} Huffman table: oversubscribed codes
- Invalid ${label} Huffman table: incomplete codes
- Invalid ${label} Huffman table: prefix collision
- Invalid ${this.#label} Huffman code
- Invalid ${this.#label} Huffman code: excessive depth
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/7441278ea470b410.
Report an issue: GitHub.