{"record":{"id":"e246d29197b95876","repo":"can1357/oh-my-pi","slug":"invalid-label-huffman-table-oversubscribed-cod","errorCode":null,"errorMessage":"Invalid ${label} Huffman table: oversubscribed codes","messagePattern":"Invalid (.+?) Huffman table: oversubscribed codes","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":81,"sourceCode":"\tstatic build(lengths: Uint8Array, symbolCount: number, label: string): CanonicalHuffman {\n\t\tconst counts = new Uint32Array(17);\n\t\tlet maximumLength = 0;\n\t\tfor (let symbol = 0; symbol < symbolCount; symbol++) {\n\t\t\tconst length = lengths[symbol]!;\n\t\t\tif (length > 16) throw new ArchiveError(`Invalid ${label} Huffman table: code is too long`);\n\t\t\tif (length !== 0) {\n\t\t\t\tcounts[length]++;\n\t\t\t\tmaximumLength = Math.max(maximumLength, length);\n\t\t\t}\n\t\t}\n\t\tif (maximumLength === 0) throw new ArchiveError(`Invalid ${label} Huffman table: no symbols`);\n\n\t\tconst nextCodes = new Uint32Array(17);\n\t\tlet code = 0;\n\t\tfor (let length = 1; length <= 16; length++) {\n\t\t\tcode = (code + counts[length - 1]!) * 2;\n\t\t\tif (code + counts[length]! > 2 ** length) {\n\t\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: oversubscribed codes`);\n\t\t\t}\n\t\t\tnextCodes[length] = code;\n\t\t}\n\t\tif (nextCodes[maximumLength]! + counts[maximumLength]! !== 2 ** maximumLength) {\n\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: incomplete codes`);\n\t\t}\n\n\t\tconst tree = new CanonicalHuffman(label);\n\t\tfor (let symbol = 0; symbol < symbolCount; symbol++) {\n\t\t\tconst length = lengths[symbol]!;\n\t\t\tif (length === 0) continue;\n\t\t\tconst symbolCode = nextCodes[length]!;\n\t\t\tnextCodes[length] = symbolCode + 1;\n\t\t\tlet node = 0;\n\t\t\tfor (let bitIndex = length - 1; bitIndex >= 0; bitIndex--) {\n\t\t\t\tif (tree.#symbol[node]! >= 0) {\n\t\t\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: prefix collision`);\n\t\t\t\t}","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L63-L99","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: trusting raw lengths read from a custom encoder\nlengths = encodeFrequencies(freqs); // may be oversubscribed\n// after: build a proper canonical length table\nconst { lengths } = packageLengths(limitedLengthHuffman(freqs, 16)); // ensures sum(counts[l]/2^l) <= 1","handlingStrategy":"try-catch","validationCode":"// No pre-call API exists: table bytes come from the archive stream.\n// Pre-validate the file before decompressing:\nconst stat = await Bun.file(archivePath).stat?.();\nif (!stat || stat.size < 24) throw new Error('archive too small to be valid LZH');","typeGuard":"function isLzhMethod(method: string): boolean {\n  return method === '-lh0-' || method === '-lh5-' || method === '-lh6-' || method === '-lh7-';\n}","tryCatchPattern":"try {\n  const out = decompressLhStatic(compressed, originalSize);\n} catch (err) {\n  if (err instanceof ArchiveError) {\n    throw new Error(`LZH archive table is corrupt: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["archive","huffman","lzh","corruption","validation"],"backgroundTag":"corrupt-huffman-table","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}