{"record":{"id":"7441278ea470b410","repo":"can1357/oh-my-pi","slug":"invalid-label-huffman-table-duplicate-code","errorCode":null,"errorMessage":"Invalid ${label} Huffman table: duplicate code","messagePattern":"Invalid (.+?) Huffman table: duplicate code","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":113,"sourceCode":"\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}\n\t\t\t\tconst bit = (symbolCode >>> bitIndex) & 1;\n\t\t\t\tlet child = bit === 0 ? tree.#zero[node]! : tree.#one[node]!;\n\t\t\t\tif (child < 0) {\n\t\t\t\t\tchild = tree.#symbol.length;\n\t\t\t\t\ttree.#zero.push(-1);\n\t\t\t\t\ttree.#one.push(-1);\n\t\t\t\t\ttree.#symbol.push(-1);\n\t\t\t\t\tif (bit === 0) tree.#zero[node] = child;\n\t\t\t\t\telse tree.#one[node] = child;\n\t\t\t\t}\n\t\t\t\tnode = child;\n\t\t\t}\n\t\t\tif (tree.#symbol[node]! >= 0 || tree.#zero[node]! >= 0 || tree.#one[node]! >= 0) {\n\t\t\t\tthrow new ArchiveError(`Invalid ${label} Huffman table: duplicate code`);\n\t\t\t}\n\t\t\ttree.#symbol[node] = symbol;\n\t\t}\n\t\treturn tree;\n\t}\n\n\tdecode(reader: MsbBitReader): number {\n\t\tlet node = 0;\n\t\tfor (let depth = 0; depth <= 16; depth++) {\n\t\t\tconst symbol = this.#symbol[node]!;\n\t\t\tif (symbol >= 0) return symbol;\n\t\t\tnode = reader.read(1) === 0 ? this.#zero[node]! : this.#one[node]!;\n\t\t\tif (node < 0) throw new ArchiveError(`Invalid ${this.#label} Huffman code`);\n\t\t}\n\t\tthrow new ArchiveError(`Invalid ${this.#label} Huffman code: excessive depth`);\n\t}\n}\n","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L95-L131","documentation":"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.","triggerScenarios":"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.","commonSituations":"Corrupt or truncated .lzh files, buggy third-party LH compressors, fuzzed inputs targeting the table parser.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const out = decompressLhStatic(data, size);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('duplicate code')) {\n    // classify and quarantine the archive\n    return null;\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["archive","huffman","lzh","corruption","duplicate-code"],"backgroundTag":"corrupt-huffman-table","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}