can1357/oh-my-pi · error · ArchiveError

Invalid ${this.#label} Huffman code

Error message

Invalid ${this.#label} Huffman code

What it means

CanonicalHuffman.decode() walks the binary tree one bit at a time; if the next-bit child link is negative (node < 0), the bit sequence read so far is not a prefix of any code in the table. The library throws ArchiveError because the encoded data stream no longer matches the declared table — the archive data is corrupt or misaligned.

Source

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

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

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;

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the archive integrity (CRC / unlha -t) and re-download or restore from backup
  2. Verify the compressed-data offset equals headerSize + 2 (method+checksum) for the LH variant in use
  3. Stop decoding at the declared compressed size instead of running to end-of-input
  4. Catch ArchiveError and report a corrupt archive; retrying will not help

Example fix

// before: decoding until reader is exhausted
decodeUntilEnd(bitReader, tree);
// after: respect the declared compressed size
const end = offset + compressedSize;
while (bitReader.byteOffset < end) output.push(tree.decode(bitReader));
Defensive patterns

Strategy: try-catch

Validate before calling

// Bounds-check against the declared compressed size before decoding:
if (data.length < declaredCompressedSize) {
  throw new Error('truncated archive: data shorter than declared size');
}

Try / catch

try {
  const out = decompressLhStatic(data, originalSize);
} catch (err) {
  if (err instanceof ArchiveError) {
    throw new Error(`archive data does not match its Huffman table: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling decompressLhStatic / temporary decoding on data where the compressed bitstream diverges from the table: corruption inside the data section, reading the data section at a wrong offset (bad header skip), or continuing to decode past the true end of the block.

Common situations: Truncated downloads cut mid-block, archives with wrong declared compressed sizes, byte-shifted parsing after a header bug, fuzz inputs.

Related errors


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