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; 16 17 import com.google.archivepatcher.shared.JreDeflateParameters; 18 import com.google.archivepatcher.shared.PatchConstants; 19 import com.google.archivepatcher.shared.TypedRange; 20 21 import org.junit.Assert; 22 import org.junit.Before; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 import org.junit.runners.JUnit4; 26 27 import java.io.ByteArrayInputStream; 28 import java.io.ByteArrayOutputStream; 29 import java.io.DataOutputStream; 30 import java.io.IOException; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.List; 35 36 /** 37 * Tests for {@link PatchReader}. 38 */ 39 @RunWith(JUnit4.class) 40 @SuppressWarnings("javadoc") 41 public class PatchReaderTest { 42 // This is Integer.MAX_VALUE + 1. 43 private static final long BIG = 2048L * 1024L * 1024L; 44 45 private static final JreDeflateParameters DEFLATE_PARAMS = JreDeflateParameters.of(6, 0, true); 46 47 private static final TypedRange<Void> OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE1 = 48 new TypedRange<Void>(BIG, 17L, null); 49 50 private static final TypedRange<Void> OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE2 = 51 new TypedRange<Void>(BIG + 25L, 19L, null); 52 53 private static final List<TypedRange<Void>> OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN = 54 Collections.unmodifiableList( 55 Arrays.asList( 56 OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE1, OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE2)); 57 58 private static final TypedRange<JreDeflateParameters> NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE1 = 59 new TypedRange<JreDeflateParameters>(BIG, BIG, DEFLATE_PARAMS); 60 61 private static final TypedRange<JreDeflateParameters> NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE2 = 62 new TypedRange<JreDeflateParameters>(BIG * 2, BIG, DEFLATE_PARAMS); 63 64 private static final List<TypedRange<JreDeflateParameters>> NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN = 65 Collections.unmodifiableList( 66 Arrays.asList( 67 NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE1, NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE2)); 68 69 private static final long DELTA_FRIENDLY_OLD_FILE_SIZE = BIG - 75L; 70 71 private static final long DELTA_FRIENDLY_NEW_FILE_SIZE = BIG + 75L; 72 73 private static final TypedRange<Void> DELTA_FRIENDLY_OLD_FILE_WORK_RANGE = 74 new TypedRange<Void>(0, DELTA_FRIENDLY_OLD_FILE_SIZE, null); 75 76 private static final TypedRange<Void> DELTA_FRIENDLY_NEW_FILE_WORK_RANGE = 77 new TypedRange<Void>(0, DELTA_FRIENDLY_NEW_FILE_SIZE, null); 78 79 private static final String DELTA_CONTENT = "all your delta are belong to us"; 80 81 private static final DeltaDescriptor DELTA_DESCRIPTOR = 82 new DeltaDescriptor( 83 PatchConstants.DeltaFormat.BSDIFF, 84 DELTA_FRIENDLY_OLD_FILE_WORK_RANGE, 85 DELTA_FRIENDLY_NEW_FILE_WORK_RANGE, 86 DELTA_CONTENT.length()); 87 88 private static final List<DeltaDescriptor> DELTA_DESCRIPTORS = 89 Collections.singletonList(DELTA_DESCRIPTOR); 90 91 private Corruption corruption = null; 92 93 /** 94 * Settings that can be altered to break the code under test in useful ways. 95 */ 96 private static class Corruption { 97 boolean corruptIdentifier = false; 98 boolean corruptDeltaFriendlyOldFileSize = false; 99 boolean corruptOldFileUncompressionInstructionCount = false; 100 boolean corruptOldFileUncompressionInstructionOffset = false; 101 boolean corruptOldFileUncompressionInstructionLength = false; 102 boolean corruptOldFileUncompressionInstructionOrder = false; 103 boolean corruptDeltaFriendlyNewFileRecompressionInstructionCount = false; 104 boolean corruptDeltaFriendlyNewFileRecompressionInstructionOffset = false; 105 boolean corruptDeltaFriendlyNewFileRecompressionInstructionLength = false; 106 boolean corruptDeltaFriendlyNewFileRecompressionInstructionOrder = false; 107 boolean corruptCompatibilityWindowId = false; 108 boolean corruptLevel = false; 109 boolean corruptStrategy = false; 110 boolean corruptNowrap = false; 111 boolean corruptNumDeltaRecords = false; 112 boolean corruptDeltaType = false; 113 boolean corruptDeltaFriendlyOldFileWorkRangeOffset = false; 114 boolean corruptDeltaFriendlyOldFileWorkRangeLength = false; 115 boolean corruptDeltaFriendlyNewFileWorkRangeOffset = false; 116 boolean corruptDeltaFriendlyNewFileWorkRangeLength = false; 117 boolean corruptDeltaLength = false; 118 } 119 120 @Before setup()121 public void setup() { 122 corruption = new Corruption(); 123 } 124 125 /** 126 * Write a test patch with the constants in this file. 127 * @return the patch, as an array of bytes. 128 * @throws IOException if something goes wrong 129 */ writeTestPatch()130 private byte[] writeTestPatch() throws IOException { 131 // --------------------------------------------------------------------------------------------- 132 // CAUTION - DO NOT CHANGE THIS FUNCTION WITHOUT DUE CONSIDERATION FOR BREAKING THE PATCH FORMAT 133 // --------------------------------------------------------------------------------------------- 134 // This test writes a simple patch with all the static data listed above and verifies that it 135 // can be read. This code MUST be INDEPENDENT of the patch writer code, even if it is partially 136 // redundant; this guards against accidental changes to the patch reader that could alter the 137 // expected format and otherwise escape detection. 138 ByteArrayOutputStream out = new ByteArrayOutputStream(); 139 DataOutputStream patchOut = new DataOutputStream(out); 140 patchOut.write( 141 corruption.corruptIdentifier 142 ? new byte[8] 143 : PatchConstants.IDENTIFIER.getBytes("US-ASCII")); // header 144 patchOut.writeInt(0); // Flags, all reserved in v1 145 patchOut.writeLong( 146 corruption.corruptDeltaFriendlyOldFileSize ? -1 : DELTA_FRIENDLY_OLD_FILE_SIZE); 147 148 // Write the uncompression instructions 149 patchOut.writeInt( 150 corruption.corruptOldFileUncompressionInstructionCount 151 ? -1 152 : OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN.size()); 153 List<TypedRange<Void>> oldDeltaFriendlyUncompressPlan = 154 new ArrayList<TypedRange<Void>>(OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN); 155 if (corruption.corruptOldFileUncompressionInstructionOrder) { 156 Collections.reverse(oldDeltaFriendlyUncompressPlan); 157 } 158 for (TypedRange<Void> range : oldDeltaFriendlyUncompressPlan) { 159 patchOut.writeLong( 160 corruption.corruptOldFileUncompressionInstructionOffset ? -1 : range.getOffset()); 161 patchOut.writeLong( 162 corruption.corruptOldFileUncompressionInstructionLength ? -1 : range.getLength()); 163 } 164 165 // Write the recompression instructions 166 patchOut.writeInt( 167 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionCount 168 ? -1 169 : NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN.size()); 170 List<TypedRange<JreDeflateParameters>> newDeltaFriendlyRecompressPlan = 171 new ArrayList<TypedRange<JreDeflateParameters>>(NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN); 172 if (corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOrder) { 173 Collections.reverse(newDeltaFriendlyRecompressPlan); 174 } 175 for (TypedRange<JreDeflateParameters> range : newDeltaFriendlyRecompressPlan) { 176 patchOut.writeLong( 177 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOffset 178 ? -1 179 : range.getOffset()); 180 patchOut.writeLong( 181 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionLength 182 ? -1 183 : range.getLength()); 184 // Now the JreDeflateParameters for the record 185 patchOut.write( 186 corruption.corruptCompatibilityWindowId 187 ? 31 188 : PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue); 189 patchOut.write(corruption.corruptLevel ? 11 : range.getMetadata().level); 190 patchOut.write(corruption.corruptStrategy ? 11 : range.getMetadata().strategy); 191 patchOut.write(corruption.corruptNowrap ? 3 : (range.getMetadata().nowrap ? 1 : 0)); 192 } 193 194 // Delta section. V1 patches have exactly one delta entry and it is always mapped to the entire 195 // file contents of the delta-friendly files. 196 patchOut.writeInt( 197 corruption.corruptNumDeltaRecords 198 ? -1 199 : DELTA_DESCRIPTORS.size()); // Number of difference records 200 for (DeltaDescriptor descriptor : DELTA_DESCRIPTORS) { 201 patchOut.write(corruption.corruptDeltaType ? 73 : descriptor.getFormat().patchValue); 202 patchOut.writeLong( 203 corruption.corruptDeltaFriendlyOldFileWorkRangeOffset 204 ? -1 205 : descriptor.getDeltaFriendlyOldFileRange().getOffset()); 206 patchOut.writeLong( 207 corruption.corruptDeltaFriendlyOldFileWorkRangeLength 208 ? -1 209 : descriptor.getDeltaFriendlyOldFileRange().getLength()); 210 patchOut.writeLong( 211 corruption.corruptDeltaFriendlyNewFileWorkRangeOffset 212 ? -1 213 : descriptor.getDeltaFriendlyNewFileRange().getOffset()); 214 patchOut.writeLong( 215 corruption.corruptDeltaFriendlyNewFileWorkRangeLength 216 ? -1 217 : descriptor.getDeltaFriendlyNewFileRange().getLength()); 218 patchOut.writeLong(corruption.corruptDeltaLength ? -1 : descriptor.getDeltaLength()); 219 } 220 221 // Finally, the delta bytes 222 patchOut.write(DELTA_CONTENT.getBytes("US-ASCII")); 223 return out.toByteArray(); 224 } 225 226 @Test testReadPatchApplyPlan()227 public void testReadPatchApplyPlan() throws IOException { 228 PatchApplyPlan plan = 229 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 230 Assert.assertEquals(DELTA_FRIENDLY_OLD_FILE_SIZE, plan.getDeltaFriendlyOldFileSize()); 231 Assert.assertEquals(OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN, plan.getOldFileUncompressionPlan()); 232 Assert.assertEquals( 233 NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN, plan.getDeltaFriendlyNewFileRecompressionPlan()); 234 Assert.assertEquals(DELTA_DESCRIPTORS, plan.getDeltaDescriptors()); 235 } 236 237 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptIdentifier()238 public void testReadPatchApplyPlan_CorruptIdentifier() throws IOException { 239 corruption.corruptIdentifier = true; 240 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 241 } 242 243 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileSize()244 public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileSize() throws IOException { 245 corruption.corruptDeltaFriendlyOldFileSize = true; 246 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 247 } 248 249 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionCount()250 public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionCount() 251 throws IOException { 252 corruption.corruptOldFileUncompressionInstructionCount = true; 253 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 254 } 255 256 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOrder()257 public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOrder() 258 throws IOException { 259 corruption.corruptOldFileUncompressionInstructionOrder = true; 260 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 261 } 262 263 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOffset()264 public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOffset() 265 throws IOException { 266 corruption.corruptOldFileUncompressionInstructionOffset = true; 267 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 268 } 269 270 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionLength()271 public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionLength() 272 throws IOException { 273 corruption.corruptOldFileUncompressionInstructionLength = true; 274 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 275 } 276 277 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionCount()278 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionCount() 279 throws IOException { 280 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionCount = true; 281 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 282 } 283 284 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOrder()285 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOrder() 286 throws IOException { 287 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOrder = true; 288 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 289 } 290 291 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOffset()292 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOffset() 293 throws IOException { 294 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOffset = true; 295 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 296 } 297 298 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionLength()299 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionLength() 300 throws IOException { 301 corruption.corruptDeltaFriendlyNewFileRecompressionInstructionLength = true; 302 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 303 } 304 305 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptCompatibilityWindowId()306 public void testReadPatchApplyPlan_CorruptCompatibilityWindowId() throws IOException { 307 corruption.corruptCompatibilityWindowId = true; 308 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 309 } 310 311 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptLevel()312 public void testReadPatchApplyPlan_CorruptLevel() throws IOException { 313 corruption.corruptLevel = true; 314 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 315 } 316 317 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptStrategy()318 public void testReadPatchApplyPlan_CorruptStrategy() throws IOException { 319 corruption.corruptStrategy = true; 320 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 321 } 322 323 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptNowrap()324 public void testReadPatchApplyPlan_CorruptNowrap() throws IOException { 325 corruption.corruptNowrap = true; 326 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 327 } 328 329 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptNumDeltaRecords()330 public void testReadPatchApplyPlan_CorruptNumDeltaRecords() throws IOException { 331 corruption.corruptNumDeltaRecords = true; 332 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 333 } 334 335 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaType()336 public void testReadPatchApplyPlan_CorruptDeltaType() throws IOException { 337 corruption.corruptDeltaType = true; 338 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 339 } 340 341 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeOffset()342 public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeOffset() 343 throws IOException { 344 corruption.corruptDeltaFriendlyOldFileWorkRangeOffset = true; 345 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 346 } 347 348 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeLength()349 public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeLength() 350 throws IOException { 351 corruption.corruptDeltaFriendlyOldFileWorkRangeLength = true; 352 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 353 } 354 355 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeOffset()356 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeOffset() 357 throws IOException { 358 corruption.corruptDeltaFriendlyNewFileWorkRangeOffset = true; 359 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 360 } 361 362 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeLength()363 public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeLength() 364 throws IOException { 365 corruption.corruptDeltaFriendlyNewFileWorkRangeLength = true; 366 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 367 } 368 369 @Test(expected = PatchFormatException.class) testReadPatchApplyPlan_DeltaLength()370 public void testReadPatchApplyPlan_DeltaLength() throws IOException { 371 corruption.corruptDeltaLength = true; 372 new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch())); 373 } 374 } 375