{"record":{"id":"8e3e81e0104b07cf","repo":"can1357/oh-my-pi","slug":"invalid-label-compressed-data-empty-block","errorCode":null,"errorMessage":"Invalid ${label} compressed data: empty block","messagePattern":"Invalid (.+?) compressed data: empty block","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/lzh.ts","lineNumber":213,"sourceCode":"/** @internal Decode the static-Huffman LZSS stream shared by LZH and ARJ methods 1-3. */\nexport function decompressLhStatic(\n\tpacked: Uint8Array,\n\toutSize: number,\n\tdictionarySize: number,\n\tpositionBits: number,\n\tpositionSymbols: number,\n\tlabel: string,\n): Uint8Array {\n\tconst reader = new MsbBitReader(packed, label);\n\tconst output = new Uint8Array(outSize);\n\tlet outputPosition = 0;\n\tlet blockRemaining = 0;\n\tlet commands: CanonicalHuffman | undefined;\n\tlet positions: CanonicalHuffman | undefined;\n\twhile (outputPosition < outSize) {\n\t\tif (blockRemaining === 0) {\n\t\t\tblockRemaining = reader.read(16);\n\t\t\tif (blockRemaining === 0) throw new ArchiveError(`Invalid ${label} compressed data: empty block`);\n\t\t\tconst temporary = readTemporaryTree(reader, label);\n\t\t\tcommands = readCommandTree(reader, temporary, label);\n\t\t\tpositions = readPositionTree(reader, positionBits, positionSymbols, label);\n\t\t}\n\t\tblockRemaining--;\n\t\tconst symbol = commands!.decode(reader);\n\t\tif (symbol < 256) {\n\t\t\toutput[outputPosition++] = symbol;\n\t\t\tcontinue;\n\t\t}\n\t\tconst length = symbol - 256 + 3;\n\t\tif (length > outSize - outputPosition) {\n\t\t\tthrow new ArchiveError(`Invalid ${label} compressed data: match exceeds declared size`);\n\t\t}\n\t\tconst positionCode = positions!.decode(reader);\n\t\tlet distance = positionCode;\n\t\tif (positionCode > 1) {\n\t\t\tconst lowBitCount = positionCode - 1;","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/lzh.ts#L195-L231","documentation":"Each Huffman block begins with a 16-bit command count. A zero count is invalid because decompression still needs to fill the output; the library throws rather than looping forever.","triggerScenarios":"decompressLhStatic reads a block whose 16-bit blockRemaining header is 0 — corrupt data, zero-filled region, or reader positioned at the wrong offset (e.g. wrong extended-header skip before data start).","commonSituations":"Truncated archives padded with zeros; incorrect handling of -lh4- vs -lh5- header differences causing offset drift; fuzzed inputs.","solutions":["Verify the compressed-data start offset (extended headers parsed/skipped correctly)","Re-obtain the archive and check CRC","Confirm the method variant is supported before decompressing","Catch ArchiveError for untrusted input"],"exampleFix":"// before\nconst data = packed.slice(headerSize); // forgot to skip extended headers\n// after\nconst data = packed.slice(headerSize + extendedHeaderBytes);","handlingStrategy":"try-catch","validationCode":"if (packed.length < 2) throw new Error(\"compressed member too short to contain a block header\");","typeGuard":null,"tryCatchPattern":"try {\n  const out = decompressLhStatic(packed, outSize, positionBits, positionSymbols, label);\n} catch (err) {\n  if (err instanceof ArchiveError) reportCorruptEntry(entryName, err.message);\n  else throw err;\n}","preventionTips":["Parse all extended headers before computing the data start offset","Check archive-level CRC/integrity first","Treat every external archive as untrusted input"],"tags":["archive","corruption","lzh"],"backgroundTag":"corrupt-archive-data","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}