can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: footer CRC32 mismatch

Error message

Invalid XZ stream: footer CRC32 mismatch

What it means

The XZ stream footer stores a CRC32 of its stream-flag bytes plus the backward-index size. discoverStreams() recomputes the CRC32 over footer bytes 4..10 and compares it to the stored value; a mismatch means the footer (or its CRC field) was modified or corrupted, so the stream cannot be trusted.

Source

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

function discoverStreams(bytes: Uint8Array): XzStream[] {
	if (bytes.byteLength === 0 || (bytes.byteLength & 3) !== 0)
		throw new ArchiveError("Invalid XZ stream: size is not a multiple of four bytes");
	const streams: XzStream[] = [];
	let end = bytes.byteLength;
	while (end > 0) {
		let padding = 0;
		while (end >= 4 && bytes[end - 1] === 0 && bytes[end - 2] === 0 && bytes[end - 3] === 0 && bytes[end - 4] === 0) {
			end -= 4;
			padding += 4;
		}
		if (end === 0) throw new ArchiveError("Invalid XZ stream: padding without a stream");
		if (end < 24) throw new ArchiveError("Invalid XZ stream: truncated stream framing");
		const footerStart = end - 12;
		if (bytes[footerStart + 10] !== 0x59 || bytes[footerStart + 11] !== 0x5a)
			throw new ArchiveError("Invalid XZ stream: footer magic mismatch");
		if (crc32(bytes.subarray(footerStart + 4, footerStart + 10)) !== read32LE(bytes, footerStart))
			throw new ArchiveError("Invalid XZ stream: footer CRC32 mismatch");
		const flag0 = bytes[footerStart + 8]!;
		const flag1 = bytes[footerStart + 9]!;
		if (flag0 !== 0 || (flag1 & 0xf0) !== 0) throw new ArchiveError("Unsupported XZ stream flags");
		const checkId = flag1 & 0x0f;
		checkSize(checkId);
		const indexSize = (read32LE(bytes, footerStart + 4) + 1) * 4;
		if (!Number.isSafeInteger(indexSize) || indexSize > footerStart)
			throw new ArchiveError("Invalid XZ stream: backward index size is invalid");
		const indexStart = footerStart - indexSize;
		const records = parseIndex(bytes, indexStart, indexSize);
		let blocksSize = 0;
		for (const record of records) {
			blocksSize += Math.ceil(record.unpaddedSize / 4) * 4;
			if (!Number.isSafeInteger(blocksSize)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
		}
		const start = indexStart - blocksSize - 12;
		if (start < 0 || start + 12 > bytes.byteLength || !equalBytes(bytes.subarray(start, start + 6), XZ_MAGIC)) {
			throw new ArchiveError("Invalid XZ stream: header position or magic is invalid");

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-download or restore the file and verify its checksum (e.g. sha256 against the source)
  2. If slicing buffers manually, verify the footer offset is exactly end-12 for the stream
  3. Re-compress the data from the original source if the original is damaged
  4. Test the file with the xz CLI (xz -t) to confirm corruption independent of this library

Example fix

// before
const slice = whole.subarray(6); // misaligned
await xzDecode(slice); // footer CRC mismatch
// after
const slice = whole.subarray(0); // keep stream-aligned offsets
await xzDecode(slice);
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-screen with an external tool when available:
// $ xz -t file.xz && echo ok

Type guard

null

Try / catch

try {
  await xzDecode(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("footer CRC32 mismatch")) {
    throw new Error("XZ footer corrupted — re-acquire the archive");
  }
  throw err;
}

Prevention

When it happens

Trigger: Any single-bit or multi-byte corruption within the 12-byte footer (CRC field at footerStart+0..3, flags at +8..9, backward size at +4..7, or the CRC-covered region), or a buffer slice misaligned by a few bytes so fields are read from wrong offsets.

Common situations: Faulty downloads/transfers, disk errors, files modified after compression (e.g. by a tool that patched bytes), or manual buffer slicing with wrong offsets in multi-stream archives.

Related errors


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