{"record":{"id":"49847a5e534c3029","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-empty-lzx-huffman-tree","errorCode":null,"errorMessage":"Invalid CAB archive: empty LZX Huffman tree","messagePattern":"Invalid CAB archive: empty LZX Huffman tree","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/lzx.ts","lineNumber":80,"sourceCode":"class LzxHuffmanTable {\n\treadonly #counts = new Uint32Array(17);\n\treadonly #firstCodes = new Uint32Array(17);\n\treadonly #firstSymbols = new Uint32Array(17);\n\treadonly #symbols: Uint16Array;\n\treadonly empty: boolean;\n\n\tconstructor(lengths: Uint8Array, allowEmpty = false) {\n\t\tlet symbolCount = 0;\n\t\tfor (const length of lengths) {\n\t\t\tif (length > 16) throw new ArchiveError(\"Invalid CAB archive: invalid LZX Huffman code length\");\n\t\t\tif (length !== 0) {\n\t\t\t\tthis.#counts[length]++;\n\t\t\t\tsymbolCount++;\n\t\t\t}\n\t\t}\n\t\tthis.empty = symbolCount === 0;\n\t\tif (this.empty && !allowEmpty) {\n\t\t\tthrow new ArchiveError(\"Invalid CAB archive: empty LZX Huffman tree\");\n\t\t}\n\n\t\tlet code = 0;\n\t\tlet symbolOffset = 0;\n\t\tfor (let length = 1; length <= 16; length++) {\n\t\t\tcode = (code + this.#counts[length - 1]!) * 2;\n\t\t\tif (code + this.#counts[length]! > 2 ** length) {\n\t\t\t\tthrow new ArchiveError(\"Invalid CAB archive: oversubscribed LZX Huffman tree\");\n\t\t\t}\n\t\t\tthis.#firstCodes[length] = code;\n\t\t\tthis.#firstSymbols[length] = symbolOffset;\n\t\t\tsymbolOffset += this.#counts[length]!;\n\t\t}\n\n\t\tthis.#symbols = new Uint16Array(symbolCount);\n\t\tconst next = this.#firstSymbols.slice();\n\t\tfor (let symbol = 0; symbol < lengths.byteLength; symbol++) {\n\t\t\tconst length = lengths[symbol]!;","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/lzx.ts#L62-L98","documentation":"This error is thrown by the LzxHuffmanTable constructor when every code length in the tree description is zero, i.e. no symbols are assigned any Huffman code. The LZX format requires a usable tree to decode the bitstream, so a tree with zero symbols is rejected unless the caller explicitly passes allowEmpty (used only for the length tree, which may legitimately be empty). It guards against corrupted or malformed CAB LZX block headers.","triggerScenarios":"Decompressing a CAB folder whose LZX block header defines a main, length, aligned-offset, or pretree where all code lengths read from the bitstream are 0 (e.g. new LzxHuffmanTable(new Uint8Array(256)) without allowEmpty).","commonSituations":"Corrupted or truncated CAB downloads, archives produced by buggy or non-conforming LZX compressors, bit-level parsing desync from an earlier bad read leaving garbage where tree lengths should be.","solutions":["Verify the CAB file integrity (checksums, re-download or re-extract the archive).","Re-create the CAB with a conformant tool (e.g. makecab/cabarc) so each Huffman tree defines at least one symbol.","If you are constructing trees manually for testing, pass allowEmpty=true or give at least one symbol a non-zero code length.","Check that the bit reader is synchronized — an earlier parsing mistake can shift bits so zero lengths are read."],"exampleFix":"// before\nnew LzxHuffmanTable(lengths) // throws when all lengths are 0\n// after\nnew LzxHuffmanTable(lengths, lengths === this.#lengthLengths) // allow empty only for the secondary length tree","handlingStrategy":"try-catch","validationCode":"// If you control the lengths, pre-check before constructing:\nconst hasSymbol = lengths.some(l => l !== 0)\nif (!hasSymbol) throw new Error('Refusing to build an empty Huffman tree')","typeGuard":"function isNonEmptyTree(lengths: Uint8Array): boolean {\n  return lengths.some(l => l !== 0)\n}","tryCatchPattern":"try {\n  const table = new LzxHuffmanTable(lengths)\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('empty LZX Huffman tree')) {\n    // treat archive as corrupt: surface a user-friendly 'archive is damaged' error\n  }\n  throw err\n}","preventionTips":["Always feed complete, in-order CFDATA block bytes to the decoder","Validate CAB checksums before decompression","Only pass allowEmpty=true for the secondary length tree, as the library itself does","Re-download rather than retry-decode corrupt archives"],"tags":["archive","cab","lzx","huffman","corruption"],"backgroundTag":"corrupt-archive-huffman-tree","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}