blob: 8bae180714d87e5c0fabe6af347090778907e6ff [file] [log] [blame]
// Copyright 2016 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.archivepatcher.applier;
import com.google.archivepatcher.shared.JreDeflateParameters;
import com.google.archivepatcher.shared.PatchConstants;
import com.google.archivepatcher.shared.TypedRange;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Tests for {@link PatchReader}.
*/
@RunWith(JUnit4.class)
@SuppressWarnings("javadoc")
public class PatchReaderTest {
// This is Integer.MAX_VALUE + 1.
private static final long BIG = 2048L * 1024L * 1024L;
private static final JreDeflateParameters DEFLATE_PARAMS = JreDeflateParameters.of(6, 0, true);
private static final TypedRange<Void> OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE1 =
new TypedRange<Void>(BIG, 17L, null);
private static final TypedRange<Void> OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE2 =
new TypedRange<Void>(BIG + 25L, 19L, null);
private static final List<TypedRange<Void>> OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN =
Collections.unmodifiableList(
Arrays.asList(
OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE1, OLD_DELTA_FRIENDLY_UNCOMPRESS_RANGE2));
private static final TypedRange<JreDeflateParameters> NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE1 =
new TypedRange<JreDeflateParameters>(BIG, BIG, DEFLATE_PARAMS);
private static final TypedRange<JreDeflateParameters> NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE2 =
new TypedRange<JreDeflateParameters>(BIG * 2, BIG, DEFLATE_PARAMS);
private static final List<TypedRange<JreDeflateParameters>> NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN =
Collections.unmodifiableList(
Arrays.asList(
NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE1, NEW_DELTA_FRIENDLY_RECOMPRESS_RANGE2));
private static final long DELTA_FRIENDLY_OLD_FILE_SIZE = BIG - 75L;
private static final long DELTA_FRIENDLY_NEW_FILE_SIZE = BIG + 75L;
private static final TypedRange<Void> DELTA_FRIENDLY_OLD_FILE_WORK_RANGE =
new TypedRange<Void>(0, DELTA_FRIENDLY_OLD_FILE_SIZE, null);
private static final TypedRange<Void> DELTA_FRIENDLY_NEW_FILE_WORK_RANGE =
new TypedRange<Void>(0, DELTA_FRIENDLY_NEW_FILE_SIZE, null);
private static final String DELTA_CONTENT = "all your delta are belong to us";
private static final DeltaDescriptor DELTA_DESCRIPTOR =
new DeltaDescriptor(
PatchConstants.DeltaFormat.BSDIFF,
DELTA_FRIENDLY_OLD_FILE_WORK_RANGE,
DELTA_FRIENDLY_NEW_FILE_WORK_RANGE,
DELTA_CONTENT.length());
private static final List<DeltaDescriptor> DELTA_DESCRIPTORS =
Collections.singletonList(DELTA_DESCRIPTOR);
private Corruption corruption = null;
/**
* Settings that can be altered to break the code under test in useful ways.
*/
private static class Corruption {
boolean corruptIdentifier = false;
boolean corruptDeltaFriendlyOldFileSize = false;
boolean corruptOldFileUncompressionInstructionCount = false;
boolean corruptOldFileUncompressionInstructionOffset = false;
boolean corruptOldFileUncompressionInstructionLength = false;
boolean corruptOldFileUncompressionInstructionOrder = false;
boolean corruptDeltaFriendlyNewFileRecompressionInstructionCount = false;
boolean corruptDeltaFriendlyNewFileRecompressionInstructionOffset = false;
boolean corruptDeltaFriendlyNewFileRecompressionInstructionLength = false;
boolean corruptDeltaFriendlyNewFileRecompressionInstructionOrder = false;
boolean corruptCompatibilityWindowId = false;
boolean corruptLevel = false;
boolean corruptStrategy = false;
boolean corruptNowrap = false;
boolean corruptNumDeltaRecords = false;
boolean corruptDeltaType = false;
boolean corruptDeltaFriendlyOldFileWorkRangeOffset = false;
boolean corruptDeltaFriendlyOldFileWorkRangeLength = false;
boolean corruptDeltaFriendlyNewFileWorkRangeOffset = false;
boolean corruptDeltaFriendlyNewFileWorkRangeLength = false;
boolean corruptDeltaLength = false;
}
@Before
public void setup() {
corruption = new Corruption();
}
/**
* Write a test patch with the constants in this file.
* @return the patch, as an array of bytes.
* @throws IOException if something goes wrong
*/
private byte[] writeTestPatch() throws IOException {
// ---------------------------------------------------------------------------------------------
// CAUTION - DO NOT CHANGE THIS FUNCTION WITHOUT DUE CONSIDERATION FOR BREAKING THE PATCH FORMAT
// ---------------------------------------------------------------------------------------------
// This test writes a simple patch with all the static data listed above and verifies that it
// can be read. This code MUST be INDEPENDENT of the patch writer code, even if it is partially
// redundant; this guards against accidental changes to the patch reader that could alter the
// expected format and otherwise escape detection.
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream patchOut = new DataOutputStream(out);
patchOut.write(
corruption.corruptIdentifier
? new byte[8]
: PatchConstants.IDENTIFIER.getBytes("US-ASCII")); // header
patchOut.writeInt(0); // Flags, all reserved in v1
patchOut.writeLong(
corruption.corruptDeltaFriendlyOldFileSize ? -1 : DELTA_FRIENDLY_OLD_FILE_SIZE);
// Write the uncompression instructions
patchOut.writeInt(
corruption.corruptOldFileUncompressionInstructionCount
? -1
: OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN.size());
List<TypedRange<Void>> oldDeltaFriendlyUncompressPlan =
new ArrayList<TypedRange<Void>>(OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN);
if (corruption.corruptOldFileUncompressionInstructionOrder) {
Collections.reverse(oldDeltaFriendlyUncompressPlan);
}
for (TypedRange<Void> range : oldDeltaFriendlyUncompressPlan) {
patchOut.writeLong(
corruption.corruptOldFileUncompressionInstructionOffset ? -1 : range.getOffset());
patchOut.writeLong(
corruption.corruptOldFileUncompressionInstructionLength ? -1 : range.getLength());
}
// Write the recompression instructions
patchOut.writeInt(
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionCount
? -1
: NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN.size());
List<TypedRange<JreDeflateParameters>> newDeltaFriendlyRecompressPlan =
new ArrayList<TypedRange<JreDeflateParameters>>(NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN);
if (corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOrder) {
Collections.reverse(newDeltaFriendlyRecompressPlan);
}
for (TypedRange<JreDeflateParameters> range : newDeltaFriendlyRecompressPlan) {
patchOut.writeLong(
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOffset
? -1
: range.getOffset());
patchOut.writeLong(
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionLength
? -1
: range.getLength());
// Now the JreDeflateParameters for the record
patchOut.write(
corruption.corruptCompatibilityWindowId
? 31
: PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue);
patchOut.write(corruption.corruptLevel ? 11 : range.getMetadata().level);
patchOut.write(corruption.corruptStrategy ? 11 : range.getMetadata().strategy);
patchOut.write(corruption.corruptNowrap ? 3 : (range.getMetadata().nowrap ? 1 : 0));
}
// Delta section. V1 patches have exactly one delta entry and it is always mapped to the entire
// file contents of the delta-friendly files.
patchOut.writeInt(
corruption.corruptNumDeltaRecords
? -1
: DELTA_DESCRIPTORS.size()); // Number of difference records
for (DeltaDescriptor descriptor : DELTA_DESCRIPTORS) {
patchOut.write(corruption.corruptDeltaType ? 73 : descriptor.getFormat().patchValue);
patchOut.writeLong(
corruption.corruptDeltaFriendlyOldFileWorkRangeOffset
? -1
: descriptor.getDeltaFriendlyOldFileRange().getOffset());
patchOut.writeLong(
corruption.corruptDeltaFriendlyOldFileWorkRangeLength
? -1
: descriptor.getDeltaFriendlyOldFileRange().getLength());
patchOut.writeLong(
corruption.corruptDeltaFriendlyNewFileWorkRangeOffset
? -1
: descriptor.getDeltaFriendlyNewFileRange().getOffset());
patchOut.writeLong(
corruption.corruptDeltaFriendlyNewFileWorkRangeLength
? -1
: descriptor.getDeltaFriendlyNewFileRange().getLength());
patchOut.writeLong(corruption.corruptDeltaLength ? -1 : descriptor.getDeltaLength());
}
// Finally, the delta bytes
patchOut.write(DELTA_CONTENT.getBytes("US-ASCII"));
return out.toByteArray();
}
@Test
public void testReadPatchApplyPlan() throws IOException {
PatchApplyPlan plan =
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
Assert.assertEquals(DELTA_FRIENDLY_OLD_FILE_SIZE, plan.getDeltaFriendlyOldFileSize());
Assert.assertEquals(OLD_DELTA_FRIENDLY_UNCOMPRESS_PLAN, plan.getOldFileUncompressionPlan());
Assert.assertEquals(
NEW_DELTA_FRIENDLY_RECOMPRESS_PLAN, plan.getDeltaFriendlyNewFileRecompressionPlan());
Assert.assertEquals(DELTA_DESCRIPTORS, plan.getDeltaDescriptors());
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptIdentifier() throws IOException {
corruption.corruptIdentifier = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileSize() throws IOException {
corruption.corruptDeltaFriendlyOldFileSize = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionCount()
throws IOException {
corruption.corruptOldFileUncompressionInstructionCount = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOrder()
throws IOException {
corruption.corruptOldFileUncompressionInstructionOrder = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionOffset()
throws IOException {
corruption.corruptOldFileUncompressionInstructionOffset = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptOldFileUncompressionInstructionLength()
throws IOException {
corruption.corruptOldFileUncompressionInstructionLength = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionCount()
throws IOException {
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionCount = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOrder()
throws IOException {
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOrder = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionOffset()
throws IOException {
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionOffset = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileRecompressionInstructionLength()
throws IOException {
corruption.corruptDeltaFriendlyNewFileRecompressionInstructionLength = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptCompatibilityWindowId() throws IOException {
corruption.corruptCompatibilityWindowId = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptLevel() throws IOException {
corruption.corruptLevel = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptStrategy() throws IOException {
corruption.corruptStrategy = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptNowrap() throws IOException {
corruption.corruptNowrap = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptNumDeltaRecords() throws IOException {
corruption.corruptNumDeltaRecords = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaType() throws IOException {
corruption.corruptDeltaType = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeOffset()
throws IOException {
corruption.corruptDeltaFriendlyOldFileWorkRangeOffset = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyOldFileWorkRangeLength()
throws IOException {
corruption.corruptDeltaFriendlyOldFileWorkRangeLength = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeOffset()
throws IOException {
corruption.corruptDeltaFriendlyNewFileWorkRangeOffset = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_CorruptDeltaFriendlyNewFileWorkRangeLength()
throws IOException {
corruption.corruptDeltaFriendlyNewFileWorkRangeLength = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
@Test(expected = PatchFormatException.class)
public void testReadPatchApplyPlan_DeltaLength() throws IOException {
corruption.corruptDeltaLength = true;
new PatchReader().readPatchApplyPlan(new ByteArrayInputStream(writeTestPatch()));
}
}