public static byte[] bsdiff(InputStream oldFileIn, InputStream newFileIn, ByteArrayOutputStream diffFileOut, int timeout) throws IOException {
    byte[] oldBuf = Utils.readBytes(oldFileIn);
    int oldSize = oldBuf.length;

    int[] I = new int[oldSize + 1];

    sort(I, oldBuf);

    byte[] newBuf = Utils.readBytes(newFileIn);
    int newSize = newBuf.length;

    // diff block
    int dbLen = 0;
    byte[] db = new byte[newSize];

    // extra block
    int ebLen = 0;
    byte[] eb = new byte[newSize];

    /*
     * Diff file is composed as follows:
     * Header (24 bytes)
     * Data (from offset 24 to end of file)
     *
     * Header:
     * Offset 0, length 8 bytes: length of ctrl block
     * Offset 8, length 8 bytes: length of compressed diff block
     * Offset 16, length 8 bytes: length of new file
     *
     * Data:
     * 24 (length ctrlBlockLen): ctrlBlock
     * 24 + ctrlBlockLen (length diffBlockLen): diffBlock (GZIPed)
     * 24 + ctrlBlockLen + diffBlockLen (to end of file): extraBlock (GZIPed)
     *
     * ctrlBlock comprises a set of records, each record 12 bytes. A record
     * comprises 3 x 32 bit integers. The ctrlBlock is not compressed.
     */

    ByteArrayOutputStream arrayOut = new OpenByteArrayOutputStream();
    DataOutputStream diffOut = new DataOutputStream(arrayOut);

    int oldScore, scSc;
    int overlap, ss, lenS;
    int scan = 0;
    int len = 0;
    int lastScan = 0;
    int lastPos = 0;
    int lastOffset = 0;

    IntByRef pos = new IntByRef();
    int ctrlBlockLen = 0;

    long stop = timeout > 0 ? System.nanoTime() + timeout * 1_000_000_000L : 0;

    while (scan < newSize) {
      if (stop != 0 && System.nanoTime() > stop) {
        diffFileOut.reset();
        return newBuf;
      }

      oldScore = 0;

      for (scSc = scan += len; scan < newSize; scan++) {
        len = search(I, oldBuf, newBuf, scan, 0, oldSize, pos);

        for (; scSc < scan + len; scSc++) {
          if ((scSc + lastOffset < oldSize)
              && (oldBuf[scSc + lastOffset] == newBuf[scSc])) {
            oldScore++;
          }
        }

        if (((len == oldScore) && (len != 0)) || (len > oldScore + 8)) {
          break;
        }

        if ((scan + lastOffset < oldSize)
            && (oldBuf[scan + lastOffset] == newBuf[scan])) {
          oldScore--;
        }
      }

      if ((len != oldScore) || (scan == newSize)) {
        int s = 0;
        int Sf = 0;
        int lenF = 0;
        for (int i = 0; (lastScan + i < scan) && (lastPos + i < oldSize); i++) {
          if (oldBuf[lastPos + i] == newBuf[lastScan + i]) {
            s++;
          }
          if (s * 2 - i + 1 > Sf * 2 - lenF) {
            Sf = s;
            lenF = i + 1;
          }
        }

        int lenB = 0;
        if (scan < newSize) {
          s = 0;
          int Sb = 0;
          for (int i = 1; (scan >= lastScan + i) && (pos.value >= i); i++) {
            if (oldBuf[pos.value - i] == newBuf[scan - i]) {
              s++;
            }
            if (s * 2 - i > Sb * 2 - lenB) {
              Sb = s;
              lenB = i;
            }
          }
        }

        if (lastScan + lenF > scan - lenB) {
          overlap = (lastScan + lenF) - (scan - lenB);
          s = 0;
          ss = 0;
          lenS = 0;
          for (int i = 0; i < overlap; i++) {
            if (newBuf[lastScan + lenF - overlap + i] == oldBuf[lastPos
                                                                + lenF - overlap + i]) {
              s++;
            }
            if (newBuf[scan - lenB + i] == oldBuf[pos.value - lenB + i]) {
              s--;
            }
            if (s > ss) {
              ss = s;
              lenS = i + 1;
            }
          }

          lenF += lenS - overlap;
          lenB -= lenS;
        }

        // ? byte casting introduced here -- might affect things
        for (int i = 0; i < lenF; i++) {
          db[dbLen + i] = (byte)(newBuf[lastScan + i] - oldBuf[lastPos + i]);
        }

        for (int i = 0; i < (scan - lenB) - (lastScan + lenF); i++) {
          eb[ebLen + i] = newBuf[lastScan + lenF + i];
        }

        dbLen += lenF;
        ebLen += (scan - lenB) - (lastScan + lenF);

        /* Write control block entry (3 x int) */
        diffOut.writeInt(lenF);
        diffOut.writeInt((scan - lenB) - (lastScan + lenF));
        diffOut.writeInt((pos.value - lenB) - (lastPos + lenF));
        ctrlBlockLen += 12;

        lastScan = scan - lenB;
        lastPos = pos.value - lenB;
        lastOffset = pos.value - scan;
      }
    }

    //noinspection UnusedAssignment
    I = null;

    /* Write diff block */
    GZIPOutputStream dbOut = new GZIPOutputStream(diffOut);
    dbOut.write(db, 0, dbLen);
    dbOut.finish();
    int diffBlockLen = diffOut.size() - ctrlBlockLen;

    /* Write extra block */
    GZIPOutputStream ebOut = new GZIPOutputStream(diffOut);
    ebOut.write(eb, 0, ebLen);
    ebOut.finish();

    diffOut.close();

    DataOutputStream headerStream = new DataOutputStream(diffFileOut);
    headerStream.writeLong(ctrlBlockLen);  // ctrlBlockLen (compressed)
    headerStream.writeLong(diffBlockLen);  // diffBlockLen (compressed)
    headerStream.writeLong(newSize);
    headerStream.flush();

    arrayOut.writeTo(diffFileOut);

    return newBuf;
  }
public static byte[] bsdiff(InputStream oldFileIn, InputStream newFileIn, ByteArrayOutputStream diffFileOut, int timeout)