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