Home | History | Annotate | Download | only in gdiff
      1 // Copyright 2016 Google Inc. All rights reserved.
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     http://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 package com.google.archivepatcher.applier.gdiff;
     16 
     17 import com.google.archivepatcher.applier.PatchFormatException;
     18 import java.io.BufferedInputStream;
     19 import java.io.DataInputStream;
     20 import java.io.EOFException;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.OutputStream;
     24 import java.io.RandomAccessFile;
     25 
     26 /** Clean implementation of http://www.w3.org/TR/NOTE-gdiff-19970901 */
     27 public class Gdiff {
     28 
     29   /** Magic bytes at start of file */
     30   private static final int GDIFF_FILE_MAGIC = 0xD1FFD1FF;
     31   /** Version code at start of file */
     32   private static final int GDIFF_FILE_VERSION = 4;
     33 
     34   /** The end of the patch */
     35   private static final int EOF = 0;
     36   /** Codes 1..246 represent inline streams of 1..246 bytes */
     37   // private static final int DATA_MIN = 1;
     38   // private static final int DATA_MAX = 246;
     39   /** Copy inline data. The next two bytes are the number of bytes to copy */
     40   private static final int DATA_USHORT = 247;
     41   /** Copy inline data. The next four bytes are the number of bytes to copy */
     42   private static final int DATA_INT = 248;
     43   /**
     44    * The copy commands are defined as follows: The first argument is the offset in the original
     45    * file, and the second argument is the number of bytes to copy to the new file.
     46    */
     47   private static final int COPY_USHORT_UBYTE = 249;
     48 
     49   private static final int COPY_USHORT_USHORT = 250;
     50   private static final int COPY_USHORT_INT = 251;
     51   private static final int COPY_INT_UBYTE = 252;
     52   private static final int COPY_INT_USHORT = 253;
     53   private static final int COPY_INT_INT = 254;
     54   private static final int COPY_LONG_INT = 255;
     55 
     56   /**
     57    * We're patching APKs which are big. We might as well use a big buffer. TODO: 64k? This would
     58    * allow us to do the USHORT copies in a single pass.
     59    */
     60   private static final int COPY_BUFFER_SIZE = 16 * 1024;
     61 
     62   /**
     63    * The patch is typically compressed and the input stream is decompressing on-the-fly. A small
     64    * buffer greatly improves efficiency on complicated patches with lots of short directives. See
     65    * b/21109650 for more information.
     66    */
     67   private static final int PATCH_STREAM_BUFFER_SIZE = 4 * 1024;
     68 
     69   /**
     70    * Apply a patch to a file.
     71    *
     72    * @param inputFile base file
     73    * @param patchFile patch file
     74    * @param output output stream to write the file to
     75    * @param expectedOutputSize expected size of the output.
     76    * @throws IOException on file I/O as well as when patch under/over run happens.
     77    */
     78   public static long patch(
     79       RandomAccessFile inputFile,
     80       InputStream patchFile,
     81       OutputStream output,
     82       long expectedOutputSize)
     83       throws IOException {
     84     byte[] buffer = new byte[COPY_BUFFER_SIZE];
     85     long outputSize = 0;
     86 
     87     // Wrap patchfile with a small buffer to cushion the 1,2,4,8 byte reads
     88     patchFile = new BufferedInputStream(patchFile, PATCH_STREAM_BUFFER_SIZE);
     89     // Use DataInputStream to help with big-endian data
     90     DataInputStream patchDataStream = new DataInputStream(patchFile);
     91     // Confirm first 4 bytes are magic signature.
     92     int magic = patchDataStream.readInt();
     93     if (magic != GDIFF_FILE_MAGIC) {
     94       throw new PatchFormatException("Unexpected magic=" + String.format("%x", magic));
     95     }
     96     // Confirm patch format version
     97     int version = patchDataStream.read();
     98     if (version != GDIFF_FILE_VERSION) {
     99       throw new PatchFormatException("Unexpected version=" + version);
    100     }
    101 
    102     try {
    103       // Start copying
    104       while (true) {
    105         int copyLength;
    106         long copyOffset;
    107         long maxCopyLength = expectedOutputSize - outputSize;
    108         int command = patchDataStream.read();
    109         switch (command) {
    110           case -1:
    111             throw new IOException("Patch file overrun");
    112           case EOF:
    113             return outputSize;
    114           case DATA_USHORT:
    115             copyLength = patchDataStream.readUnsignedShort();
    116             copyFromPatch(buffer, patchDataStream, output, copyLength, maxCopyLength);
    117             break;
    118           case DATA_INT:
    119             copyLength = patchDataStream.readInt();
    120             copyFromPatch(buffer, patchDataStream, output, copyLength, maxCopyLength);
    121             break;
    122           case COPY_USHORT_UBYTE:
    123             copyOffset = patchDataStream.readUnsignedShort();
    124             copyLength = patchDataStream.read();
    125             if (copyLength == -1) {
    126               throw new IOException("Unexpected end of patch");
    127             }
    128             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    129             break;
    130           case COPY_USHORT_USHORT:
    131             copyOffset = patchDataStream.readUnsignedShort();
    132             copyLength = patchDataStream.readUnsignedShort();
    133             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    134             break;
    135           case COPY_USHORT_INT:
    136             copyOffset = patchDataStream.readUnsignedShort();
    137             copyLength = patchDataStream.readInt();
    138             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    139             break;
    140           case COPY_INT_UBYTE:
    141             copyOffset = patchDataStream.readInt();
    142             copyLength = patchDataStream.read();
    143             if (copyLength == -1) {
    144               throw new IOException("Unexpected end of patch");
    145             }
    146             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    147             break;
    148           case COPY_INT_USHORT:
    149             copyOffset = patchDataStream.readInt();
    150             copyLength = patchDataStream.readUnsignedShort();
    151             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    152             break;
    153           case COPY_INT_INT:
    154             copyOffset = patchDataStream.readInt();
    155             copyLength = patchDataStream.readInt();
    156             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    157             break;
    158           case COPY_LONG_INT:
    159             copyOffset = patchDataStream.readLong();
    160             copyLength = patchDataStream.readInt();
    161             copyFromOriginal(buffer, inputFile, output, copyOffset, copyLength, maxCopyLength);
    162             break;
    163           default:
    164             // The only possible bytes remaining are DATA_MIN through DATA_MAX,
    165             // barring any programming error.
    166             copyLength = command;
    167             copyFromPatch(buffer, patchDataStream, output, copyLength, maxCopyLength);
    168             break;
    169         }
    170         outputSize += copyLength;
    171       }
    172     } finally {
    173       output.flush();
    174     }
    175   }
    176 
    177   /** Copy a series of inline bytes from the patch file to the output file */
    178   private static void copyFromPatch(
    179       byte[] buffer,
    180       DataInputStream patchDataStream,
    181       OutputStream output,
    182       int copyLength,
    183       long maxCopyLength)
    184       throws IOException {
    185     if (copyLength < 0) {
    186       throw new IOException("copyLength negative");
    187     }
    188     if (copyLength > maxCopyLength) {
    189       throw new IOException("Output length overrun");
    190     }
    191     try {
    192       while (copyLength > 0) {
    193         int spanLength = (copyLength < COPY_BUFFER_SIZE) ? copyLength : COPY_BUFFER_SIZE;
    194         patchDataStream.readFully(buffer, 0, spanLength);
    195         output.write(buffer, 0, spanLength);
    196         copyLength -= spanLength;
    197       }
    198     } catch (EOFException e) {
    199       throw new IOException("patch underrun");
    200     }
    201   }
    202 
    203   /** Copy a series of bytes from the input (original) file to the output file */
    204   private static void copyFromOriginal(
    205       byte[] buffer,
    206       RandomAccessFile inputFile,
    207       OutputStream output,
    208       long inputOffset,
    209       int copyLength,
    210       long maxCopyLength)
    211       throws IOException {
    212     if (copyLength < 0) {
    213       throw new IOException("copyLength negative");
    214     }
    215     if (inputOffset < 0) {
    216       throw new IOException("inputOffset negative");
    217     }
    218     if (copyLength > maxCopyLength) {
    219       throw new IOException("Output length overrun");
    220     }
    221     try {
    222       inputFile.seek(inputOffset);
    223       while (copyLength > 0) {
    224         int spanLength = (copyLength < COPY_BUFFER_SIZE) ? copyLength : COPY_BUFFER_SIZE;
    225         inputFile.readFully(buffer, 0, spanLength);
    226         output.write(buffer, 0, spanLength);
    227         copyLength -= spanLength;
    228       }
    229     } catch (EOFException e) {
    230       throw new IOException("patch underrun", e);
    231     }
    232   }
    233 }
    234