Tencent/tinker · critical · IOException

Corrupt by wrong patch file.

Error message

Corrupt by wrong patch file.

What it means

BSPatch.apply streams control triples (add-length, copy-length, seek) and applies them to produce the new file. The first control value ctrl[0] says how many bytes the diff block contributes at the current new-file position; if newpos + ctrl[0] would overrun newsize, the patch's control stream disagrees with the declared output size, so the patch cannot be applied to this input. This is a hard integrity check that prevents writing past the end of the output buffer.

Solutions

  1. Verify the old file's digest matches the digest recorded when the patch was created (tinker patches carry old-file MD5s); if not, fetch or locate the correct base version.
  2. Verify the patch file's MD5/SHA against the value from the server before calling BSPatch; re-download on mismatch.
  3. Regenerate the patch from the exact old/new pair with the same bsdiff/bspatch implementation pair.
  4. If corruption recurs, check storage/transport: disk full, interrupted download, or a proxy mangling binary payloads.
Defensive patterns

Strategy: validation

Validate before calling

// Verify old-file and patch digests before applying a bsdiff patch
boolean safeToPatch(java.io.File oldFile, java.io.File patchFile,
                    String expectedOldMd5, String expectedPatchMd5) throws Exception {
    return md5Of(oldFile).equals(expectedOldMd5)
        && md5Of(patchFile).equals(expectedPatchMd5);
}
String md5Of(java.io.File f) throws Exception {
    java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
    try (java.io.InputStream in = new java.io.FileInputStream(f)) {
        byte[] buf = new byte[8192]; int n;
        while ((n = in.read(buf)) > 0) md.update(buf, 0, n);
    }
    StringBuilder sb = new StringBuilder();
    for (byte b : md.digest()) sb.append(String.format("%02x", b));
    return sb.toString();
}

Try / catch

try {
    byte[] out = com.tencent.tinker.bsdiff.BSPatch.patch(oldData, newDataLen, patchStream);
} catch (java.io.IOException e) {
    if ("Corrupt by wrong patch file.".equals(e.getMessage())) {
        // do NOT retry with the same inputs; re-verify digests, re-download patch or correct base file
        throw new IllegalStateException("patch/base mismatch: verify old-file and patch MD5s", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling BSPatch.patch (or its variants) where the control triple's first entry would push newpos beyond newsize: a patch file truncated or byte-corrupted in its control block, a patch applied to an old input that is not the exact base it was diffed against, or a patch generated by an incompatible bsdiff version/format.

Common situations: OTA/patch applied to the wrong base APK version; patch file corrupted during download (partial transfer, proxy truncation); MD5 of the patch not verified before apply; mixing bsdiff/bspatch builds with different control-block endianness or header layouts.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/cbd9b35ab49719fe. Report an issue: GitHub.

Appendix: source

Thrown at third-party/bsdiff-util/src/main/java/com/tencent/tinker/bsdiff/BSPatch.java:347

        in.skip(diffBlockLen + ctrlBlockLen + BSUtil.HEADER_SIZE);
        InputStream extraBlockIn = new GZIPInputStream(in);

        // byte[] newBuf = new byte[newsize + 1];
        byte[] newBuf = new byte[newsize];

        int oldpos = 0;
        int newpos = 0;
        int[] ctrl = new int[3];

        // int nbytes;
        while (newpos < newsize) {

            for (int i = 0; i <= 2; i++) {
                ctrl[i] = ctrlBlockIn.readInt();
            }

            if (newpos + ctrl[0] > newsize) {
                throw new IOException("Corrupt by wrong patch file.");
            }

            // Read ctrl[0] bytes from diffBlock stream
            if (!BSUtil.readFromStream(diffBlockIn, newBuf, newpos, ctrl[0])) {
                throw new IOException("Corrupt by wrong patch file.");
            }

            for (int i = 0; i < ctrl[0]; i++) {
                if ((oldpos + i >= 0) && (oldpos + i < oldsize)) {
                    newBuf[newpos + i] += oldBuf[oldpos + i];
                }
            }

            newpos += ctrl[0];
            oldpos += ctrl[0];

            if (newpos + ctrl[1] > newsize) {
                throw new IOException("Corrupt by wrong patch file.");

View on GitHub (pinned to 1b7ea02c23)