can1357/oh-my-pi · error · ArchiveError

Invalid ${this.#label} Huffman code: excessive depth

Error message

Invalid ${this.#label} Huffman code: excessive depth

What it means

decode() also enforces a hard depth cap of 16 bits (LZH's maximum code length). If no leaf is reached after consuming 16 bits, the code is longer than any the table permits, so the stream cannot correspond to this table. This is a safety net that also prevents infinite loops on pathological/corrupt data.

Source

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

				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`);
	}
}

function readCodeLength(reader: MsbBitReader, label: string): number {
	let length = reader.read(3);
	if (length === 7) {
		while (reader.read(1) !== 0) {
			length++;
			if (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);
		}
	}
	return length;
}

function readTemporaryTree(reader: MsbBitReader, label: string): CanonicalHuffman {
	const symbolCount = 19;
	const encodedCount = reader.read(5);
	if (encodedCount === 0) return CanonicalHuffman.single(reader.read(5), symbolCount, label);

View on GitHub (pinned to 9690622007)

Solutions

  1. Treat the archive as corrupt: verify externally and re-obtain a clean copy
  2. Recheck where the table section ends and the data section begins (9-bit command count / 5-bit temp count boundaries)
  3. Do not retry on the same bytes; ArchiveError here is deterministic
  4. If you generate archives, cap code lengths at 16 bits (package-merge) so stream and table always agree
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const out = decompressLhStatic(data, size);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes('excessive depth')) {
    throw new Error('archive stream/table mismatch (corrupt data)');
  }
  throw err;
}

Prevention

When it happens

Trigger: Corrupt data bits forming a never-terminating path, or a table built with max length < 16 while the data contains longer patterns — always a stream/table mismatch, seen via decompressLhStatic or temporary-tree decoding on damaged input.

Common situations: Severely corrupted or scrambled archives, wrong-offset parsing where data bytes are actually still table bytes, fuzzed inputs.

Related errors


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