can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: truncated integer

Error message

Invalid XZ stream: truncated integer

What it means

read32LE reads a little-endian uint32 from a byte buffer and throws when the 4-byte range [offset, offset+4) falls outside the buffer. The XZ stream being parsed is too short at the expected field location.

Source

Thrown at packages/utils/src/ar/codecs/xz.ts:8

import { crc32, crc64 } from "../checksums";
import { ArchiveError } from "../error";
import { lzma2Decompress } from "./lzma";

const XZ_MAGIC = Uint8Array.of(0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00);

function read32LE(bytes: Uint8Array, offset: number): number {
	if (offset < 0 || offset + 4 > bytes.byteLength) throw new ArchiveError("Invalid XZ stream: truncated integer");
	return (bytes[offset]! | (bytes[offset + 1]! << 8) | (bytes[offset + 2]! << 16) | (bytes[offset + 3]! << 24)) >>> 0;
}

function write32LE(bytes: Uint8Array, offset: number, value: number): void {
	bytes[offset] = value & 0xff;
	bytes[offset + 1] = (value >>> 8) & 0xff;
	bytes[offset + 2] = (value >>> 16) & 0xff;
	bytes[offset + 3] = value >>> 24;
}

function read32BE(bytes: Uint8Array, offset: number): number {
	return ((bytes[offset]! << 24) | (bytes[offset + 1]! << 16) | (bytes[offset + 2]! << 8) | bytes[offset + 3]!) >>> 0;
}

function write32BE(bytes: Uint8Array, offset: number, value: number): void {
	bytes[offset] = value >>> 24;
	bytes[offset + 1] = (value >>> 16) & 0xff;
	bytes[offset + 2] = (value >>> 8) & 0xff;

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the file size — a valid .xz stream is at least 12 bytes (6-byte magic + 2-byte header + footer).
  2. Re-download the file; compare byte size with the source.
  3. Ensure you pass the complete stream/padded-size region to the parser, not a truncated slice.

Example fix

// before
const footer = bytes.subarray(bytes.length - 6); // may be < 6 bytes if truncated
parseFooter(footer);
// after
if (bytes.byteLength < 12) throw new Error("XZ stream too short");
const footer = bytes.subarray(bytes.length - 6);
Defensive patterns

Strategy: validation

Validate before calling

if (!bytes || bytes.byteLength < 12) {
  throw new Error("Input is not a complete XZ stream (minimum 12 bytes)");
}
if (!XZ_MAGIC.every((b, i) => bytes[i] === b)) {
  throw new Error("Input does not start with XZ magic");
}

Type guard

function isCompleteXzStream(bytes) {
  return bytes instanceof Uint8Array && bytes.byteLength >= 12;
}

Try / catch

try {
  const streams = discoverStreams(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("truncated integer")) {
    throw new Error("XZ input is truncated — re-download the file");
  }
  throw err;
}

Prevention

When it happens

Trigger: parseIndex, discoverStreams, or indexSize reads a 32-bit field (stream footer/header, CRC, index field) whose offset is beyond the supplied bytes — e.g. a stream shorter than 12 bytes or an index past EOF.

Common situations: Truncated .xz download, concatenating/parsing a byte slice that cuts a stream mid-structure, padding-only input, or passing a partial buffer instead of the full stream.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/9c545d01a4d36ca6. Report an issue: GitHub.