{"record":{"id":"d8e34f01cca25716","repo":"can1357/oh-my-pi","slug":"invalid-arj-method-4-compressed-data-truncated-bi","errorCode":null,"errorMessage":"Invalid ARJ method-4 compressed data: truncated bitstream","messagePattern":"Invalid ARJ method-4 compressed data: truncated bitstream","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/arj.ts","lineNumber":104,"sourceCode":"\t\t\tthrow new ArchiveError(\"Invalid ARJ extended header CRC32\");\n\t\t}\n\t\tcursor += extensionSize + 4;\n\t\tassertIndexSize(cursor - offset, options.limits, \"header metadata\");\n\t}\n\treturn { bodyStart, bodySize, nextOffset: cursor, metadataSize: cursor - offset, isEnd: false };\n}\n\nclass ArjBitReader {\n\treadonly #bytes: Uint8Array;\n\t#position = 0;\n\n\tconstructor(bytes: Uint8Array) {\n\t\tthis.#bytes = bytes;\n\t}\n\n\tread(count: number): number {\n\t\tif (!Number.isInteger(count) || count < 0 || count > 24 || this.#position + count > this.#bytes.byteLength * 8) {\n\t\t\tthrow new ArchiveError(\"Invalid ARJ method-4 compressed data: truncated bitstream\");\n\t\t}\n\t\tlet value = 0;\n\t\tfor (let index = 0; index < count; index++) {\n\t\t\tconst position = this.#position++;\n\t\t\tvalue = value * 2 + ((this.#bytes[position >>> 3]! >>> (7 - (position & 7))) & 1);\n\t\t}\n\t\treturn value;\n\t}\n\n\tassertZeroPadding(): void {\n\t\twhile (this.#position < this.#bytes.byteLength * 8) {\n\t\t\tif (this.read(1) !== 0) throw new ArchiveError(\"Invalid ARJ method-4 compressed data: non-zero trailing bits\");\n\t\t}\n\t}\n}\n\nfunction decompressArjMethod4(packed: Uint8Array, outSize: number): Uint8Array {\n\tconst reader = new ArjBitReader(packed);","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/arj.ts#L86-L122","documentation":"ARJ compression method 4 (the built-in variant this reader decompresses) uses an MSB-first bit reader over the packed stream. read(count) throws when the stream runs out of bits before `count` are available (or count is not an integer in 0..24), meaning the compressed data is truncated relative to what the decoder still needs.","triggerScenarios":"decompressArjMethod4() decodes more symbols than the packed stream can supply: packed data was truncated, outSize does not match the actual compressed stream, or the packed bytes were altered so the Huffman/LZ state machine consumes bits off the end.","commonSituations":"A partially downloaded or truncated member (compressed size in the header larger than the bytes actually present), a corrupted stream mid-decode, or a hand-spliced extraction where the packed payload was copied with the wrong length.","solutions":["Check the member's compressed size in the index against the actual available bytes; re-download the archive if shorter.","Test the archive externally (`arj t` / 7-Zip) to confirm which member is corrupt.","Re-extract that member with a reference ARJ tool; if it also fails, the packed data is unrecoverable — restore from backup.","If you slice packed data out yourself, copy exactly the compressedSize bytes the header declares, not a rounded or padded amount."],"exampleFix":"// before: slicing packed bytes with a wrong length\nconst packed = bytes.subarray(dataStart, dataStart + guessedSize);\n// after: use the declared compressed size and bounds-check against the buffer\nconst packed = bytes.subarray(dataStart, dataStart + entry.compressedSize);\nif (packed.byteLength !== entry.compressedSize) {\n  throw new Error(`Member ${entry.name}: truncated packed data`);\n}","handlingStrategy":"validation","validationCode":"// Ensure the member's full packed payload is present before decompressing\nfunction packedDataComplete(bytes: Uint8Array, dataOffset: number, compressedSize: number): boolean {\n  return dataOffset >= 0 && compressedSize >= 0 && dataOffset + compressedSize <= bytes.byteLength;\n}","typeGuard":null,"tryCatchPattern":"try {\n  return decompressArjMethod4(packed, originalSize);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"truncated bitstream\")) {\n    throw new Error(`Member data truncated: expected ${packed.byteLength}+ bits of packed data`);\n  }\n  throw err;\n}","preventionTips":["Slice packed payloads using the header's declared compressedSize, never guessed lengths.","Detect truncated downloads via overall file size/sha256 before extraction.","Keep per-member extraction failures isolated so one bad member does not abort the batch."],"tags":["archive","decompression","truncated-data","corrupt-file"],"backgroundTag":"truncated-compressed-stream","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}