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

  1. Re-extract the archive from a trusted source and verify checksums
  2. Confirm the table region offset is correct (no header-size misparse shifting the bitstream)
  3. If implementing a compressor, derive lengths via standard package-merge/limit-length Huffman rather than ad-hoc assignment
  4. 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

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


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