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