├── .classpath ├── .gitignore ├── .project ├── LICENSE ├── README.md ├── applier ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── applier │ │ ├── DeltaApplier.java │ │ ├── DeltaDescriptor.java │ │ ├── FileByFileV1DeltaApplier.java │ │ ├── LimitedInputStream.java │ │ ├── PartiallyCompressingOutputStream.java │ │ ├── PatchApplyPlan.java │ │ ├── PatchFormatException.java │ │ ├── PatchReader.java │ │ ├── bsdiff │ │ ├── BsDiffDeltaApplier.java │ │ └── BsPatch.java │ │ └── gdiff │ │ └── Gdiff.java │ └── test │ ├── java │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── applier │ │ ├── FileByFileV1DeltaApplierTest.java │ │ ├── LimitedInputStreamTest.java │ │ ├── PartiallyCompressingOutputStreamTest.java │ │ ├── PatchReaderTest.java │ │ ├── bsdiff │ │ └── BsPatchTest.java │ │ └── gdiff │ │ └── GdiffTest.java │ └── resources │ └── com │ └── google │ └── archivepatcher │ └── applier │ └── bsdiff │ └── testdata │ ├── bsdifftest_internal_blob_a.bin │ ├── bsdifftest_internal_blob_b.bin │ ├── bsdifftest_internal_patch_a_to_b.bin │ ├── bsdifftest_minimal_blob_a.bin │ ├── bsdifftest_minimal_blob_b.bin │ ├── bsdifftest_minimal_patch_a_to_b.bin │ ├── bsdifftest_partial_a.txt │ └── bsdifftest_partial_b.bin ├── build.gradle ├── explainer ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── explainer │ │ ├── EntryExplanation.java │ │ ├── PatchExplainer.java │ │ └── PatchExplanation.java │ └── test │ └── java │ └── com │ └── google │ └── archivepatcher │ └── explainer │ ├── PatchExplainerTest.java │ └── PatchExplanationTest.java ├── generator ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── generator │ │ ├── ByteArrayHolder.java │ │ ├── DefaultDeflateCompressionDiviner.java │ │ ├── DeltaFriendlyOldBlobSizeLimiter.java │ │ ├── DeltaGenerator.java │ │ ├── FileByFileV1DeltaGenerator.java │ │ ├── MatchingOutputStream.java │ │ ├── MinimalCentralDirectoryMetadata.java │ │ ├── MinimalZipArchive.java │ │ ├── MinimalZipEntry.java │ │ ├── MinimalZipParser.java │ │ ├── MismatchException.java │ │ ├── PatchWriter.java │ │ ├── PreDiffExecutor.java │ │ ├── PreDiffPlan.java │ │ ├── PreDiffPlanner.java │ │ ├── QualifiedRecommendation.java │ │ ├── Recommendation.java │ │ ├── RecommendationModifier.java │ │ ├── RecommendationReason.java │ │ ├── TempFileHolder.java │ │ ├── TotalRecompressionLimiter.java │ │ ├── bsdiff │ │ ├── BsDiff.java │ │ ├── BsDiffDeltaGenerator.java │ │ ├── BsDiffMatcher.java │ │ ├── BsDiffPatchWriter.java │ │ ├── BsUtil.java │ │ ├── DivSuffixSorter.java │ │ ├── Matcher.java │ │ ├── RandomAccessObject.java │ │ ├── RandomAccessObjectFactory.java │ │ └── SuffixSorter.java │ │ └── similarity │ │ ├── Crc32SimilarityFinder.java │ │ └── SimilarityFinder.java │ └── test │ ├── java │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── generator │ │ ├── ByteArrayHolderTest.java │ │ ├── DefaultDeflateCompressionDivinerTest.java │ │ ├── DeltaFriendlyOldBlobSizeLimiterTest.java │ │ ├── FileByFileV1DeltaGeneratorTest.java │ │ ├── MatchingOutputStreamTest.java │ │ ├── MinimalZipArchiveTest.java │ │ ├── MinimalZipEntryTest.java │ │ ├── MinimalZipParserTest.java │ │ ├── PatchWriterTest.java │ │ ├── PreDiffExecutorTest.java │ │ ├── PreDiffPlanTest.java │ │ ├── PreDiffPlannerTest.java │ │ ├── QualifiedRecommendationTest.java │ │ ├── TempFileHolderTest.java │ │ ├── TotalRecompressionLimiterTest.java │ │ └── bsdiff │ │ ├── BsDiffTest.java │ │ ├── BsDiffTestData.java │ │ ├── BsUtilTest.java │ │ ├── DivSuffixSorterTest.java │ │ ├── RandomAccessObjectTest.java │ │ └── SuffixSorterTestBase.java │ └── resources │ └── com │ └── google │ └── archivepatcher │ └── generator │ └── bsdiff │ └── testdata │ ├── BsDiffInternalTestNew.txt │ ├── BsDiffInternalTestOld.txt │ ├── BsDiffInternalTestPatchExpected.patch │ ├── minimalBlobA.bin │ ├── minimalBlobB.bin │ └── minimalBlobPatch.patch ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── integrationtest ├── build.gradle └── src │ └── test │ └── java │ └── com │ └── google │ └── archivepatcher │ └── integrationtest │ └── FileByFileV1IntegrationTest.java ├── sample └── src │ └── main │ └── java │ └── com │ └── google │ └── archivepatcher │ └── sample │ ├── SamplePatchApplier.java │ └── SamplePatchGenerator.java ├── settings.gradle ├── shared ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── google │ │ └── archivepatcher │ │ └── shared │ │ ├── ByteArrayInputStreamFactory.java │ │ ├── Compressor.java │ │ ├── CountingOutputStream.java │ │ ├── DefaultDeflateCompatibilityWindow.java │ │ ├── DeflateCompressor.java │ │ ├── DeflateUncompressor.java │ │ ├── DeltaFriendlyFile.java │ │ ├── JreDeflateParameters.java │ │ ├── MultiViewInputStreamFactory.java │ │ ├── PartiallyUncompressingPipe.java │ │ ├── PatchConstants.java │ │ ├── RandomAccessFileInputStream.java │ │ ├── RandomAccessFileInputStreamFactory.java │ │ ├── RandomAccessFileOutputStream.java │ │ ├── TypedRange.java │ │ └── Uncompressor.java │ └── test │ └── java │ └── com │ └── google │ └── archivepatcher │ └── shared │ ├── CountingOutputStreamTest.java │ ├── DefaultDeflateCompatibilityWindowTest.java │ ├── DeflateCompressorTest.java │ ├── DeflateUncompressorTest.java │ ├── JreDeflateParametersTest.java │ ├── PartiallyUncompressingPipeTest.java │ ├── RandomAccessFileInputStreamFactoryTest.java │ ├── RandomAccessFileInputStreamTest.java │ ├── RandomAccessFileOutputStreamTest.java │ └── TypedRangeTest.java ├── sharedtest ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── google │ └── archivepatcher │ └── shared │ ├── UnitTestZipArchive.java │ └── UnitTestZipEntry.java └── tools ├── build.gradle └── src └── main └── java └── com └── google └── archivepatcher └── tools ├── AbstractTool.java ├── FileByFileTool.java └── PatchExplainerTool.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin 3 | .settings 4 | build 5 | .gradle 6 | local-build-repo 7 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | archive-patcher 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /applier/build.gradle: -------------------------------------------------------------------------------- 1 | // applier module 2 | 3 | apply plugin: 'java' 4 | 5 | dependencies { 6 | compile project(':shared') 7 | 8 | testCompile 'junit:junit:4.12' 9 | testCompile project(':sharedtest') 10 | } 11 | 12 | task copyTestResources(type: Copy) { 13 | // AS/IntelliJ workaround: https://code.google.com/p/android/issues/detail?id=64887#c26 14 | if (System.properties['idea.platform.prefix'] != null) { 15 | from sourceSets.test.resources 16 | into sourceSets.test.output.classesDir 17 | } 18 | } 19 | 20 | processTestResources.dependsOn copyTestResources 21 | 22 | // EOF 23 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/DeltaApplier.java: -------------------------------------------------------------------------------- 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 java.io.File; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.OutputStream; 21 | 22 | /** 23 | * An interface to be implemented by delta appliers. 24 | */ 25 | public interface DeltaApplier { 26 | /** 27 | * Applies a delta from deltaIn to oldBlob and writes the result to newBlobOut. 28 | * 29 | * @param oldBlob the old blob 30 | * @param deltaIn the delta to apply to the oldBlob 31 | * @param newBlobOut the stream to write the result to 32 | * @throws IOException in the event of an I/O error reading the input or writing the output 33 | */ 34 | public void applyDelta(File oldBlob, InputStream deltaIn, OutputStream newBlobOut) 35 | throws IOException; 36 | } 37 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/DeltaDescriptor.java: -------------------------------------------------------------------------------- 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.PatchConstants; 18 | import com.google.archivepatcher.shared.PatchConstants.DeltaFormat; 19 | 20 | import com.google.archivepatcher.shared.TypedRange; 21 | 22 | /** 23 | * Describes all of the information needed to apply a single delta operation - the format of the 24 | * delta, the ranges in the delta-friendly old and new files that serve as inputs and outputs, and 25 | * the number of bytes that the delta comprises in the patch stream. 26 | */ 27 | public class DeltaDescriptor { 28 | /** 29 | * The format of the delta. 30 | */ 31 | private final PatchConstants.DeltaFormat format; 32 | 33 | /** 34 | * The work range for the delta-friendly old file. 35 | */ 36 | private final TypedRange deltaFriendlyOldFileRange; 37 | 38 | /** 39 | * The work range for the delta-friendly new file. 40 | */ 41 | private final TypedRange deltaFriendlyNewFileRange; 42 | 43 | /** 44 | * The number of bytes of delta data in the patch stream. 45 | */ 46 | private final long deltaLength; 47 | 48 | /** 49 | * Constructs a new descriptor having the specified data. 50 | * @param format the format of the delta 51 | * @param deltaFriendlyOldFileRange the work range for the delta-friendly old file 52 | * @param deltaFriendlyNewFileRange the work range for the delta-friendly new file 53 | * @param deltaLength the number of bytes of delta data in the patch stream 54 | */ 55 | public DeltaDescriptor( 56 | DeltaFormat format, 57 | TypedRange deltaFriendlyOldFileRange, 58 | TypedRange deltaFriendlyNewFileRange, 59 | long deltaLength) { 60 | this.format = format; 61 | this.deltaFriendlyOldFileRange = deltaFriendlyOldFileRange; 62 | this.deltaFriendlyNewFileRange = deltaFriendlyNewFileRange; 63 | this.deltaLength = deltaLength; 64 | } 65 | 66 | /** 67 | * Returns the format of the delta. 68 | * @return as described 69 | */ 70 | public PatchConstants.DeltaFormat getFormat() { 71 | return format; 72 | } 73 | 74 | /** 75 | * Returns the work range for the delta-friendly old file. 76 | * @return as described 77 | */ 78 | public TypedRange getDeltaFriendlyOldFileRange() { 79 | return deltaFriendlyOldFileRange; 80 | } 81 | 82 | /** 83 | * Returns the work range for the delta-friendly new file. 84 | * @return as described 85 | */ 86 | public TypedRange getDeltaFriendlyNewFileRange() { 87 | return deltaFriendlyNewFileRange; 88 | } 89 | 90 | /** 91 | * Returns the number of bytes of delta data in the patch stream. 92 | * @return as described 93 | */ 94 | public long getDeltaLength() { 95 | return deltaLength; 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | final int prime = 31; 101 | int result = 1; 102 | result = 103 | prime * result 104 | + ((deltaFriendlyNewFileRange == null) ? 0 : deltaFriendlyNewFileRange.hashCode()); 105 | result = 106 | prime * result 107 | + ((deltaFriendlyOldFileRange == null) ? 0 : deltaFriendlyOldFileRange.hashCode()); 108 | result = prime * result + (int) (deltaLength ^ (deltaLength >>> 32)); 109 | result = prime * result + ((format == null) ? 0 : format.hashCode()); 110 | return result; 111 | } 112 | 113 | @Override 114 | public boolean equals(Object obj) { 115 | if (this == obj) return true; 116 | if (obj == null) return false; 117 | if (getClass() != obj.getClass()) return false; 118 | DeltaDescriptor other = (DeltaDescriptor) obj; 119 | if (deltaFriendlyNewFileRange == null) { 120 | if (other.deltaFriendlyNewFileRange != null) return false; 121 | } else if (!deltaFriendlyNewFileRange.equals(other.deltaFriendlyNewFileRange)) return false; 122 | if (deltaFriendlyOldFileRange == null) { 123 | if (other.deltaFriendlyOldFileRange != null) return false; 124 | } else if (!deltaFriendlyOldFileRange.equals(other.deltaFriendlyOldFileRange)) return false; 125 | if (deltaLength != other.deltaLength) return false; 126 | if (format != other.format) return false; 127 | return true; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/FileByFileV1DeltaApplier.java: -------------------------------------------------------------------------------- 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.applier.bsdiff.BsDiffDeltaApplier; 18 | import com.google.archivepatcher.shared.DeltaFriendlyFile; 19 | import com.google.archivepatcher.shared.RandomAccessFileOutputStream; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.OutputStream; 24 | 25 | /** 26 | * Applies V1 patches. 27 | */ 28 | public class FileByFileV1DeltaApplier implements DeltaApplier { 29 | 30 | /** 31 | * Default size of the buffer to use for copying bytes in the recompression stream. 32 | */ 33 | private static final int DEFAULT_COPY_BUFFER_SIZE = 32768; 34 | 35 | /** 36 | * The temp directory to use. 37 | */ 38 | private final File tempDir; 39 | 40 | /** 41 | * Creates a new delta applier that will use the default temp directory for working files. This is 42 | * equivalent to calling {@link #FileByFileV1DeltaApplier(File)} with a null file 43 | * argument. 44 | */ 45 | public FileByFileV1DeltaApplier() { 46 | this(null); 47 | } 48 | 49 | /** 50 | * Creates a new delta applier that will use the specified temp directory. 51 | * 52 | * @param tempDir a temp directory where the delta-friendly old blob can be written during the 53 | * patch application process; if null, the system's default temporary directory is used 54 | */ 55 | public FileByFileV1DeltaApplier(File tempDir) { 56 | if (tempDir == null) { 57 | tempDir = new File(System.getProperty("java.io.tmpdir")); 58 | } 59 | this.tempDir = tempDir; 60 | } 61 | 62 | @Override 63 | public void applyDelta(File oldBlob, InputStream deltaIn, OutputStream newBlobOut) 64 | throws IOException { 65 | if (!tempDir.exists()) { 66 | // Be nice, try to create the temp directory. Don't bother to check return value as the code 67 | // will fail when it tries to create the file in a few more lines anyways. 68 | tempDir.mkdirs(); 69 | } 70 | File tempFile = File.createTempFile("gfbfv1", "old", tempDir); 71 | try { 72 | applyDeltaInternal(oldBlob, tempFile, deltaIn, newBlobOut); 73 | } finally { 74 | tempFile.delete(); 75 | } 76 | } 77 | 78 | /** 79 | * Does the work for applying a delta. 80 | * @param oldBlob the old blob 81 | * @param deltaFriendlyOldBlob the location in which to store the delta-friendly old blob 82 | * @param deltaIn the patch stream 83 | * @param newBlobOut the stream to write the new blob to after applying the delta 84 | * @throws IOException if anything goes wrong 85 | */ 86 | private void applyDeltaInternal( 87 | File oldBlob, File deltaFriendlyOldBlob, InputStream deltaIn, OutputStream newBlobOut) 88 | throws IOException { 89 | 90 | // First, read the patch plan from the patch stream. 91 | PatchReader patchReader = new PatchReader(); 92 | PatchApplyPlan plan = patchReader.readPatchApplyPlan(deltaIn); 93 | writeDeltaFriendlyOldBlob(plan, oldBlob, deltaFriendlyOldBlob); 94 | // Apply the delta. In v1 there is always exactly one delta descriptor, it is bsdiff, and it 95 | // takes up the rest of the patch stream - so there is no need to examine the list of 96 | // DeltaDescriptors in the patch at all. 97 | long deltaLength = plan.getDeltaDescriptors().get(0).getDeltaLength(); 98 | DeltaApplier deltaApplier = getDeltaApplier(); 99 | // Don't close this stream, as it is just a limiting wrapper. 100 | @SuppressWarnings("resource") 101 | LimitedInputStream limitedDeltaIn = new LimitedInputStream(deltaIn, deltaLength); 102 | // Don't close this stream, as it would close the underlying OutputStream (that we don't own). 103 | @SuppressWarnings("resource") 104 | PartiallyCompressingOutputStream recompressingNewBlobOut = 105 | new PartiallyCompressingOutputStream( 106 | plan.getDeltaFriendlyNewFileRecompressionPlan(), 107 | newBlobOut, 108 | DEFAULT_COPY_BUFFER_SIZE); 109 | deltaApplier.applyDelta(deltaFriendlyOldBlob, limitedDeltaIn, recompressingNewBlobOut); 110 | recompressingNewBlobOut.flush(); 111 | } 112 | 113 | /** 114 | * Writes the delta-friendly old blob to temporary storage. 115 | * @param plan the plan to use for uncompressing 116 | * @param oldBlob the blob to turn into a delta-friendly blob 117 | * @param deltaFriendlyOldBlob where to write the blob 118 | * @throws IOException if anything goes wrong 119 | */ 120 | private void writeDeltaFriendlyOldBlob( 121 | PatchApplyPlan plan, File oldBlob, File deltaFriendlyOldBlob) throws IOException { 122 | RandomAccessFileOutputStream deltaFriendlyOldFileOut = null; 123 | try { 124 | deltaFriendlyOldFileOut = 125 | new RandomAccessFileOutputStream( 126 | deltaFriendlyOldBlob, plan.getDeltaFriendlyOldFileSize()); 127 | DeltaFriendlyFile.generateDeltaFriendlyFile( 128 | plan.getOldFileUncompressionPlan(), 129 | oldBlob, 130 | deltaFriendlyOldFileOut, 131 | false, 132 | DEFAULT_COPY_BUFFER_SIZE); 133 | } finally { 134 | try { 135 | deltaFriendlyOldFileOut.close(); 136 | } catch (Exception ignored) { 137 | // Nothing 138 | } 139 | } 140 | } 141 | 142 | /** 143 | * Return an instance of a {@link DeltaApplier} suitable for applying the deltas within the patch 144 | * stream. 145 | * @return the applier 146 | */ 147 | // Visible for testing only 148 | protected DeltaApplier getDeltaApplier() { 149 | return new BsDiffDeltaApplier(); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/LimitedInputStream.java: -------------------------------------------------------------------------------- 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 java.io.FilterInputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | 21 | /** 22 | * A stream that simulates EOF after a specified numToRead of bytes has been reached. 23 | */ 24 | public class LimitedInputStream extends FilterInputStream { 25 | /** 26 | * Current numToRead. 27 | */ 28 | private long numToRead; 29 | 30 | /** 31 | * Buffer used for one-byte reads to keep all code on the same path. 32 | */ 33 | private byte[] ONE_BYTE = new byte[1]; 34 | 35 | /** 36 | * Creates a new limited stream that delegates operations to the specified stream. 37 | * @param in the stream to limit 38 | * @param numToRead the number of reads to allow before returning EOF 39 | */ 40 | public LimitedInputStream(InputStream in, long numToRead) { 41 | super(in); 42 | if (numToRead < 0) { 43 | throw new IllegalArgumentException("numToRead must be >= 0: " + numToRead); 44 | } 45 | this.numToRead = numToRead; 46 | } 47 | 48 | @Override 49 | public int read() throws IOException { 50 | if (read(ONE_BYTE, 0, 1) == 1) { 51 | return ONE_BYTE[0]; 52 | } 53 | return -1; 54 | } 55 | 56 | @Override 57 | public int read(byte[] b) throws IOException { 58 | return read(b, 0, b.length); 59 | } 60 | 61 | @Override 62 | public int read(byte[] b, int off, int len) throws IOException { 63 | if (numToRead == 0) { 64 | return -1; // Simulate EOF 65 | } 66 | int maxRead = (int) Math.min(len, numToRead); 67 | int numRead = in.read(b, off, maxRead); 68 | if (numRead > 0) { 69 | numToRead -= numRead; 70 | } 71 | return numRead; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/PatchApplyPlan.java: -------------------------------------------------------------------------------- 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.TypedRange; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | * A plan for transforming the old file prior to applying the delta and for recompressing the 24 | * delta-friendly new file afterwards, along with information on the deltas to be applied. 25 | *

26 | * The plan for uncompressing the old file is a {@link List} of {@link TypedRange} entries with void 27 | * metadata. This describes the chunks of the old file that need to be uncompressed prior to 28 | * applying the delta, in file order. The file produced by executing this plan is the 29 | * "delta-friendly" old file. 30 | *

31 | * The plan for recompressing the delta-friendly new file is a {@link List} of {@link TypedRange} 32 | * entries with {@link JreDeflateParameters} metadata. This describes the chunks of the 33 | * delta-friendly new file that need to be recompressed after diffing, again in file order. 34 | * The {@link JreDeflateParameters} metadata indicate the settings to use during recompression. The 35 | * file produced by executing this plan is the new file. 36 | *

37 | * The "plan" for the deltas themselves is a {@link List} of {@link DeltaDescriptor} entries that 38 | * describe the deltas present in the patch stream. Nominally, a {@link PatchReader} is used to 39 | * read the stream up to the first byte of the deltas; the plan for the deltas is ordered in the 40 | * same order as the patch stream and contains the byte length of each delta, so it is then trivial 41 | * to read each delta in order and apply it. 42 | */ 43 | public class PatchApplyPlan { 44 | /** 45 | * The plan for uncompressing the old file, in file order. 46 | */ 47 | private final List> oldFileUncompressionPlan; 48 | 49 | /** 50 | * The plan for recompressing the delta-friendly new file, in file order. 51 | */ 52 | private final List> deltaFriendlyNewFileRecompressionPlan; 53 | 54 | /** 55 | * The expected size of the delta-friendly old file after executing the 56 | * {@link #oldFileUncompressionPlan}. 57 | */ 58 | private final long deltaFriendlyOldFileSize; 59 | 60 | /** 61 | * The delta descriptors that describe how and what to do to the delta-friendly old file. 62 | */ 63 | private final List deltaDescriptors; 64 | 65 | /** 66 | * Constructs a new plan. 67 | * @param oldFileUncompressionPlan the plan for uncompressing the old file, in file order 68 | * @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, after 69 | * executing the plan in oldFileUncompressionPlan; this can be used to pre-allocate the necessary 70 | * space to hold the delta-friendly old file 71 | * @param deltaFriendlyNewFileRecompressionPlan the plan for recompressing the delta-friendly new 72 | * file, in file order 73 | * @param deltaDescriptors the descriptors for the deltas in the patch stream 74 | */ 75 | public PatchApplyPlan( 76 | List> oldFileUncompressionPlan, 77 | long deltaFriendlyOldFileSize, 78 | List> deltaFriendlyNewFileRecompressionPlan, 79 | List deltaDescriptors) { 80 | this.oldFileUncompressionPlan = oldFileUncompressionPlan; 81 | this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize; 82 | this.deltaFriendlyNewFileRecompressionPlan = deltaFriendlyNewFileRecompressionPlan; 83 | this.deltaDescriptors = deltaDescriptors; 84 | } 85 | 86 | /** 87 | * Returns the old file uncompression plan. 88 | * @return as described 89 | */ 90 | public List> getOldFileUncompressionPlan() { 91 | return oldFileUncompressionPlan; 92 | } 93 | 94 | /** 95 | * Returns the delta-friendly new file recompression plan. 96 | * @return as described 97 | */ 98 | public List> getDeltaFriendlyNewFileRecompressionPlan() { 99 | return deltaFriendlyNewFileRecompressionPlan; 100 | } 101 | 102 | /** 103 | * Returns the expected size of the delta-friendly old file after executing the plan returned by 104 | * {@link #getOldFileUncompressionPlan()}. This can be used to pre-allocate the necessary space to 105 | * hold the delta-friendly old file. 106 | * @return as described 107 | */ 108 | public long getDeltaFriendlyOldFileSize() { 109 | return deltaFriendlyOldFileSize; 110 | } 111 | 112 | /** 113 | * Returns the delta descriptors that describe how and what to do to the delta-friendly old file. 114 | * @return as described 115 | */ 116 | public List getDeltaDescriptors() { 117 | return deltaDescriptors; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/PatchFormatException.java: -------------------------------------------------------------------------------- 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 java.io.IOException; 18 | 19 | /** 20 | * Thrown when there is an error in the format of a patch. 21 | */ 22 | @SuppressWarnings("serial") 23 | public class PatchFormatException extends IOException { 24 | 25 | /** 26 | * Constructs a new exception with the specified message. 27 | * @param message the message 28 | */ 29 | public PatchFormatException(String message) { 30 | super(message); 31 | } 32 | 33 | /** 34 | * Constructs a new exception with the specified message and cause. 35 | * @param message the message 36 | * @param cause the cause of the error 37 | */ 38 | public PatchFormatException(String message, Throwable cause) { 39 | super(message); 40 | initCause(cause); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /applier/src/main/java/com/google/archivepatcher/applier/bsdiff/BsDiffDeltaApplier.java: -------------------------------------------------------------------------------- 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.bsdiff; 16 | 17 | import com.google.archivepatcher.applier.DeltaApplier; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.OutputStream; 23 | import java.io.RandomAccessFile; 24 | 25 | /** 26 | * An implementation of {@link DeltaApplier} that uses {@link BsPatch} to apply a bsdiff patch. 27 | */ 28 | public class BsDiffDeltaApplier implements DeltaApplier { 29 | 30 | @Override 31 | public void applyDelta(File oldBlob, InputStream deltaIn, OutputStream newBlobOut) 32 | throws IOException { 33 | RandomAccessFile oldBlobRaf = null; 34 | try { 35 | oldBlobRaf = new RandomAccessFile(oldBlob, "r"); 36 | BsPatch.applyPatch(oldBlobRaf, newBlobOut, deltaIn); 37 | } finally { 38 | try { 39 | oldBlobRaf.close(); 40 | } catch (Exception ignored) { 41 | // Nothing 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /applier/src/test/java/com/google/archivepatcher/applier/LimitedInputStreamTest.java: -------------------------------------------------------------------------------- 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 org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | 25 | /** 26 | * Tests for {@link PartiallyCompressingOutputStream}. 27 | */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class LimitedInputStreamTest { 31 | /** 32 | * An input stream that never ends and always does nothing. 33 | */ 34 | private static class ForeverInputStream extends InputStream { 35 | @Override 36 | public int read() throws IOException { 37 | return 0; 38 | } 39 | 40 | @Override 41 | public int read(byte[] b) throws IOException { 42 | return b.length; 43 | } 44 | 45 | @Override 46 | public int read(byte[] b, int off, int len) throws IOException { 47 | return len; 48 | } 49 | } 50 | 51 | @SuppressWarnings("resource") 52 | @Test 53 | public void testRead_WithLimit0() throws IOException { 54 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 0); 55 | Assert.assertEquals(-1, stream.read()); 56 | } 57 | 58 | @SuppressWarnings("resource") 59 | @Test 60 | public void testRead_WithLimit1() throws IOException { 61 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 1); 62 | Assert.assertEquals(0, stream.read()); 63 | Assert.assertEquals(-1, stream.read()); 64 | } 65 | 66 | @SuppressWarnings("resource") 67 | @Test 68 | public void testRead_WithLimit100() throws IOException { 69 | LimitedInputStream stream = new LimitedInputStream(new ForeverInputStream(), 100); 70 | Assert.assertEquals(100, stream.read(new byte[1000])); 71 | Assert.assertEquals(-1, stream.read()); 72 | } 73 | 74 | @SuppressWarnings("resource") 75 | @Test(expected = IllegalArgumentException.class) 76 | public void testSetLimit_BadValue() { 77 | new LimitedInputStream(new ForeverInputStream(), -1); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /applier/src/test/resources/com/google/archivepatcher/applier/bsdiff/testdata/bsdifftest_internal_blob_a.bin: -------------------------------------------------------------------------------- 1 | TL?I"a)tAARPRLH)ZNuYRBZ>+A2lO;D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk}#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bKo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk} evaluationDependsOn( subproject.path ) } 21 | jar.dependsOn subprojects.tasks['classes'] 22 | jar { 23 | manifest { 24 | attributes 'Implementation-Title': 'Archive Patcher', 25 | 'Implementation-Version': version, 26 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher' 27 | } 28 | baseName = project.name 29 | subprojects.each { subproject -> 30 | from subproject.sourceSets.main.output 31 | } 32 | from "LICENSE" 33 | } 34 | 35 | task sourcesJar(type: Jar, dependsOn: classes) { 36 | manifest { 37 | attributes 'Implementation-Title': 'Archive Patcher', 38 | 'Implementation-Version': version, 39 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher' 40 | } 41 | baseName = project.name 42 | classifier = 'sources' 43 | subprojects.each { subproject -> 44 | from subproject.sourceSets.main.allSource 45 | } 46 | from "LICENSE" 47 | } 48 | 49 | task javadocJar (type: Jar, dependsOn: javadoc) { 50 | manifest { 51 | attributes 'Implementation-Title': 'Archive Patcher', 52 | 'Implementation-Version': version, 53 | 'Implementation-Vendor': 'https://github.com/andrewhayden/archive-patcher' 54 | } 55 | baseName = project.name 56 | classifier = 'javadoc' 57 | subprojects.each { subproject -> 58 | from subproject.javadoc.destinationDir 59 | } 60 | from "LICENSE" 61 | } 62 | 63 | dependencies { 64 | compile project(':tools') 65 | } 66 | 67 | allprojects { 68 | repositories { 69 | jcenter() 70 | } 71 | } 72 | 73 | artifacts { 74 | archives jar 75 | archives sourcesJar 76 | archives javadocJar 77 | } 78 | 79 | publishing { 80 | publications { 81 | MyPublication(MavenPublication) { 82 | from components.java 83 | groupId 'com.google' 84 | artifactId 'archivepatcher' 85 | version version 86 | } 87 | } 88 | } 89 | 90 | bintray { 91 | user = System.getenv('BINTRAY_USER') 92 | key = System.getenv('BINTRAY_KEY') 93 | publications = ['MyPublication'] 94 | filesSpec { 95 | from 'build/libs' 96 | into '/' 97 | } 98 | pkg { 99 | repo = 'Maven' 100 | name = 'archive-patcher' 101 | desc = 'Archive Patcher' 102 | licenses = ['Apache-2.0'] 103 | websiteUrl = 'https://github.com/andrewhayden/archive-patcher' 104 | issueTrackerUrl = 'https://github.com/andrewhayden/archive-patcher/issues' 105 | vcsUrl = 'https://github.com/andrewhayden/archive-patcher.git' 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /explainer/build.gradle: -------------------------------------------------------------------------------- 1 | // explainer module 2 | apply plugin: 'java' 3 | 4 | dependencies { 5 | compile project(':generator') 6 | compile project(':shared') 7 | 8 | testCompile 'junit:junit:4.12' 9 | testCompile project(':sharedtest') 10 | } 11 | // EOF 12 | -------------------------------------------------------------------------------- /explainer/src/main/java/com/google/archivepatcher/explainer/EntryExplanation.java: -------------------------------------------------------------------------------- 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.explainer; 16 | 17 | import com.google.archivepatcher.generator.ByteArrayHolder; 18 | import com.google.archivepatcher.generator.RecommendationReason; 19 | 20 | /** 21 | * The explanation for a single entry that was considered during generation of patch. 22 | */ 23 | public class EntryExplanation { 24 | /** 25 | * The path of the entry in the new archive. 26 | */ 27 | private final ByteArrayHolder path; 28 | 29 | /** 30 | * True if the entry only exists in the new archive. 31 | */ 32 | private final boolean isNew; 33 | 34 | /** 35 | * If the entry is not new, the reason for its inclusion in or exclusion from the patch. 36 | */ 37 | private final RecommendationReason reasonIncludedIfNotNew; 38 | 39 | /** 40 | * The approximate size of the entry in the patch stream. 41 | */ 42 | private final long compressedSizeInPatch; 43 | 44 | /** 45 | * Construct a new explanation for an entry. 46 | * @param path the path of the entry in the new archive 47 | * @param isNew true if the entry only exists in the new archive 48 | * @param reasonIncludedIfNotNew when isNew is false, the reason that the entry is included 49 | * @param compressedSizeInPatch the approximate size of the entry in the patch 50 | * stream 51 | */ 52 | public EntryExplanation( 53 | ByteArrayHolder path, 54 | boolean isNew, 55 | RecommendationReason reasonIncludedIfNotNew, 56 | long compressedSizeInPatch) { 57 | super(); 58 | this.path = path; 59 | this.isNew = isNew; 60 | this.reasonIncludedIfNotNew = reasonIncludedIfNotNew; 61 | this.compressedSizeInPatch = compressedSizeInPatch; 62 | } 63 | 64 | /** 65 | * Returns the path of the entry in the new archive. 66 | * @return as described 67 | */ 68 | public ByteArrayHolder getPath() { 69 | return path; 70 | } 71 | 72 | /** 73 | * Returns true if the entry only exists in the new archive. 74 | * @return as described 75 | */ 76 | public boolean isNew() { 77 | return isNew; 78 | } 79 | 80 | /** 81 | * When {@link #isNew()} is false, the reason that the entry is included. 82 | * @return as described 83 | */ 84 | public RecommendationReason getReasonIncludedIfNotNew() { 85 | return reasonIncludedIfNotNew; 86 | } 87 | 88 | /** 89 | * Returns the approximate size of the entry in the patch stream. This number is 90 | * not guaranteed to be precise. Patch generation is complex, and in some cases 91 | * the patching process may use arbitrary bytes from arbitrary locations in the old archive to 92 | * populate bytes in the new archive. Other factors may also contribute to inaccuracies, such as 93 | * overhead in the patch format itself or in compression technology, etceteras. 94 | * @return as described 95 | */ 96 | public long getCompressedSizeInPatch() { 97 | return compressedSizeInPatch; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /generator/build.gradle: -------------------------------------------------------------------------------- 1 | // generator module 2 | 3 | apply plugin: 'java' 4 | 5 | dependencies { 6 | compile project(':shared') 7 | 8 | testCompile 'junit:junit:4.12' 9 | testCompile project(':sharedtest') 10 | } 11 | 12 | task copyTestResources(type: Copy) { 13 | // AS/IntelliJ workaround: https://code.google.com/p/android/issues/detail?id=64887#c26 14 | if (System.properties['idea.platform.prefix'] != null) { 15 | from sourceSets.test.resources 16 | into sourceSets.test.output.classesDir 17 | } 18 | } 19 | 20 | processTestResources.dependsOn copyTestResources 21 | 22 | // EOF 23 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/ByteArrayHolder.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.util.Arrays; 18 | 19 | /** 20 | * Holds an array of bytes, implementing {@link #equals(Object)}, {@link #hashCode()} with deep 21 | * comparisons. This is intended primarily to allow raw, uninterpreted paths from 22 | * {@link MinimalZipEntry#getFileNameBytes()} to be used as map keys safely. 23 | */ 24 | public class ByteArrayHolder { 25 | /** 26 | * The backing byte array. 27 | */ 28 | private final byte[] data; 29 | 30 | /** 31 | * Construct a new wrapper around the specified bytes. 32 | * @param data the byte array 33 | */ 34 | public ByteArrayHolder(byte[] data) { 35 | this.data = data; 36 | } 37 | 38 | /** 39 | * Returns the actual byte array that backs the text. 40 | * @return the array 41 | */ 42 | public byte[] getData() { 43 | return data; 44 | } 45 | 46 | @Override 47 | public int hashCode() { 48 | final int prime = 31; 49 | int result = 1; 50 | result = prime * result + Arrays.hashCode(data); 51 | return result; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object obj) { 56 | if (this == obj) return true; 57 | if (obj == null) return false; 58 | if (getClass() != obj.getClass()) return false; 59 | ByteArrayHolder other = (ByteArrayHolder) obj; 60 | if (!Arrays.equals(data, other.data)) return false; 61 | return true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/DeltaFriendlyOldBlobSizeLimiter.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.File; 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.Comparator; 21 | import java.util.List; 22 | 23 | /** 24 | * Limits the size of the delta-friendly old blob, which is an implicit limitation on the amount of 25 | * temp space required to apply a patch. 26 | * 27 | *

This class implements the following algorithm: 28 | * 29 | *

    30 | *
  1. Check the size of the old archive and subtract it from the maximum size, this is the number 31 | * of bytes that can be used to uncompress entries in the delta-friendly old file. 32 | *
  2. Identify all of the {@link QualifiedRecommendation}s that have {@link 33 | * Recommendation#uncompressOldEntry} set to true. These identify all the entries 34 | * that would be uncompressed in the delta-friendly old file. 35 | *
  3. Sort those {@link QualifiedRecommendation}s in order of decreasing uncompressed size. 36 | *
  4. Iterate over the list in order. For each entry, calculate the difference between the 37 | * uncompressed size and the compressed size; this is the number of bytes that would be 38 | * consumed to transform the data from compressed to uncompressed in the delta-friendly old 39 | * file. If the number of bytes that would be consumed is less than the number of bytes 40 | * remaining before hitting the cap, retain it; else, discard it. 41 | *
  5. Return the resulting list of the retained entries. Note that the order of this list may not 42 | * be the same as the input order (i.e., it has been sorted in order of decreasing compressed 43 | * size). 44 | *
45 | */ 46 | public class DeltaFriendlyOldBlobSizeLimiter implements RecommendationModifier { 47 | 48 | /** The maximum size of the delta-friendly old blob. */ 49 | private final long maxSizeBytes; 50 | 51 | private static final Comparator COMPARATOR = 52 | new UncompressedOldEntrySizeComparator(); 53 | 54 | /** 55 | * Create a new limiter that will restrict the total size of the delta-friendly old blob. 56 | * 57 | * @param maxSizeBytes the maximum size of the delta-friendly old blob 58 | */ 59 | public DeltaFriendlyOldBlobSizeLimiter(long maxSizeBytes) { 60 | if (maxSizeBytes < 0) { 61 | throw new IllegalArgumentException("maxSizeBytes must be non-negative: " + maxSizeBytes); 62 | } 63 | this.maxSizeBytes = maxSizeBytes; 64 | } 65 | 66 | @Override 67 | public List getModifiedRecommendations( 68 | File oldFile, File newFile, List originalRecommendations) { 69 | 70 | List sorted = sortRecommendations(originalRecommendations); 71 | 72 | List result = new ArrayList<>(sorted.size()); 73 | long bytesRemaining = maxSizeBytes - oldFile.length(); 74 | for (QualifiedRecommendation originalRecommendation : sorted) { 75 | if (!originalRecommendation.getRecommendation().uncompressOldEntry) { 76 | // Keep the original recommendation, no need to track size since it won't be uncompressed. 77 | result.add(originalRecommendation); 78 | } else { 79 | long extraBytesConsumed = 80 | originalRecommendation.getOldEntry().getUncompressedSize() 81 | - originalRecommendation.getOldEntry().getCompressedSize(); 82 | if (bytesRemaining - extraBytesConsumed >= 0) { 83 | // Keep the original recommendation, but also subtract from the remaining space. 84 | result.add(originalRecommendation); 85 | bytesRemaining -= extraBytesConsumed; 86 | } else { 87 | // Update the recommendation to prevent uncompressing this tuple. 88 | result.add( 89 | new QualifiedRecommendation( 90 | originalRecommendation.getOldEntry(), 91 | originalRecommendation.getNewEntry(), 92 | Recommendation.UNCOMPRESS_NEITHER, 93 | RecommendationReason.RESOURCE_CONSTRAINED)); 94 | } 95 | } 96 | } 97 | return result; 98 | } 99 | 100 | private static List sortRecommendations( 101 | List originalRecommendations) { 102 | List sorted = 103 | new ArrayList(originalRecommendations); 104 | Collections.sort(sorted, COMPARATOR); 105 | Collections.reverse(sorted); 106 | return sorted; 107 | } 108 | 109 | /** Helper class implementing the sort order described in the class documentation. */ 110 | private static class UncompressedOldEntrySizeComparator 111 | implements Comparator { 112 | @Override 113 | public int compare(QualifiedRecommendation qr1, QualifiedRecommendation qr2) { 114 | return Long.compare( 115 | qr1.getOldEntry().getUncompressedSize(), qr2.getOldEntry().getUncompressedSize()); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/DeltaGenerator.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * An interface to be implemented by delta generators. 23 | */ 24 | public interface DeltaGenerator { 25 | /** 26 | * Generates a delta in deltaOut that can be applied to oldBlob to produce newBlob. 27 | * 28 | * @param oldBlob the old blob 29 | * @param newBlob the new blob 30 | * @param deltaOut the stream to write the delta to 31 | * @throws IOException in the event of an I/O error reading the input files or writing to the 32 | * delta output stream 33 | * @throws InterruptedException if any thread has interrupted the current thread 34 | */ 35 | public void generateDelta(File oldBlob, File newBlob, OutputStream deltaOut) 36 | throws IOException, InterruptedException; 37 | } 38 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/FileByFileV1DeltaGenerator.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.generator.bsdiff.BsDiffDeltaGenerator; 18 | import java.io.BufferedOutputStream; 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import java.util.Arrays; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | /** 28 | * Generates file-by-file patches. 29 | */ 30 | public class FileByFileV1DeltaGenerator implements DeltaGenerator { 31 | 32 | /** Optional modifiers for planning and patch generation. */ 33 | private final List recommendationModifiers; 34 | 35 | /** 36 | * Constructs a new generator for File-by-File v1 patches, using the specified configuration. 37 | * 38 | * @param recommendationModifiers optionally, {@link RecommendationModifier}s to use for modifying 39 | * the planning phase of patch generation. These can be used to, e.g., limit the total amount 40 | * of recompression that a patch applier needs to do. Modifiers are applied in the order they 41 | * are specified. 42 | */ 43 | public FileByFileV1DeltaGenerator(RecommendationModifier... recommendationModifiers) { 44 | if (recommendationModifiers != null) { 45 | this.recommendationModifiers = 46 | Collections.unmodifiableList(Arrays.asList(recommendationModifiers)); 47 | } else { 48 | this.recommendationModifiers = Collections.emptyList(); 49 | } 50 | } 51 | 52 | /** 53 | * Generate a V1 patch for the specified input files and write the patch to the specified {@link 54 | * OutputStream}. The written patch is raw, i.e. it has not been compressed. Compression 55 | * should almost always be applied to the patch, either right in the specified {@link 56 | * OutputStream} or in a post-processing step, prior to transmitting the patch to the patch 57 | * applier. 58 | * 59 | * @param oldFile the original old file to read (will not be modified) 60 | * @param newFile the original new file to read (will not be modified) 61 | * @param patchOut the stream to write the patch to 62 | * @throws IOException if unable to complete the operation due to an I/O error 63 | * @throws InterruptedException if any thread has interrupted the current thread 64 | */ 65 | @Override 66 | public void generateDelta(File oldFile, File newFile, OutputStream patchOut) 67 | throws IOException, InterruptedException { 68 | try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder(); 69 | TempFileHolder deltaFriendlyNewFile = new TempFileHolder(); 70 | TempFileHolder deltaFile = new TempFileHolder(); 71 | FileOutputStream deltaFileOut = new FileOutputStream(deltaFile.file); 72 | BufferedOutputStream bufferedDeltaOut = new BufferedOutputStream(deltaFileOut)) { 73 | PreDiffExecutor.Builder builder = 74 | new PreDiffExecutor.Builder() 75 | .readingOriginalFiles(oldFile, newFile) 76 | .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file); 77 | for (RecommendationModifier modifier : recommendationModifiers) { 78 | builder.withRecommendationModifier(modifier); 79 | } 80 | PreDiffExecutor executor = builder.build(); 81 | PreDiffPlan preDiffPlan = executor.prepareForDiffing(); 82 | DeltaGenerator deltaGenerator = getDeltaGenerator(); 83 | deltaGenerator.generateDelta( 84 | deltaFriendlyOldFile.file, deltaFriendlyNewFile.file, bufferedDeltaOut); 85 | bufferedDeltaOut.close(); 86 | PatchWriter patchWriter = 87 | new PatchWriter( 88 | preDiffPlan, 89 | deltaFriendlyOldFile.file.length(), 90 | deltaFriendlyNewFile.file.length(), 91 | deltaFile.file); 92 | patchWriter.writeV1Patch(patchOut); 93 | } 94 | } 95 | 96 | /** 97 | * Generate a V1 patch pre diffing plan. 98 | * 99 | * @param oldFile the original old file to read (will not be modified) 100 | * @param newFile the original new file to read (will not be modified) 101 | * @return the plan 102 | * @throws IOException if unable to complete the operation due to an I/O error 103 | * @throws InterruptedException if any thread has interrupted the current thread 104 | */ 105 | public PreDiffPlan generatePreDiffPlan(File oldFile, File newFile) 106 | throws IOException, InterruptedException { 107 | try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder(); 108 | TempFileHolder deltaFriendlyNewFile = new TempFileHolder()) { 109 | PreDiffExecutor.Builder builder = 110 | new PreDiffExecutor.Builder() 111 | .readingOriginalFiles(oldFile, newFile) 112 | .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file); 113 | for (RecommendationModifier modifier : recommendationModifiers) { 114 | builder.withRecommendationModifier(modifier); 115 | } 116 | 117 | PreDiffExecutor executor = builder.build(); 118 | 119 | return executor.prepareForDiffing(); 120 | } 121 | } 122 | 123 | // Visible for testing only 124 | protected DeltaGenerator getDeltaGenerator() { 125 | return new BsDiffDeltaGenerator(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/MatchingOutputStream.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * A simple {@link OutputStream} that requires all bytes that are written to match bytes from a 23 | * specified corresponding {@link InputStream}. Any length or content mismatch results in the stream 24 | * being closed and an error being raised. 25 | *

26 | * Every call to one of the write(...) methods results in reading the same total number of bytes 27 | * from the {@link InputStream}. The bytes in both streams must match exactly. 28 | */ 29 | public class MatchingOutputStream extends OutputStream { 30 | 31 | /** 32 | * The bytes to match against. 33 | */ 34 | private final InputStream expectedBytesStream; 35 | 36 | /** 37 | * The buffer for reading bytes from the input stream for matching. 38 | */ 39 | private final byte[] buffer; 40 | 41 | /** 42 | * Constructs a new stream that will match against the the specified {@link InputStream}. 43 | * @param expectedBytesStream stream of bytes to expect to see 44 | * @param matchBufferSize the number of bytes to reserve for matching against the specified 45 | * {@link InputStream}. This 46 | */ 47 | public MatchingOutputStream(InputStream expectedBytesStream, int matchBufferSize) { 48 | if (matchBufferSize < 1) { 49 | throw new IllegalArgumentException("buffer size must be >= 1"); 50 | } 51 | this.expectedBytesStream = expectedBytesStream; 52 | this.buffer = new byte[matchBufferSize]; 53 | } 54 | 55 | @Override 56 | public void write(int b) throws IOException { 57 | int expected = expectedBytesStream.read(); 58 | if (expected == -1) { 59 | throw new MismatchException("EOF reached in expectedBytesStream"); 60 | } 61 | if (expected != b) { 62 | throw new MismatchException("Data does not match"); 63 | } 64 | } 65 | 66 | @Override 67 | public void write(byte[] b) throws IOException { 68 | write(b, 0, b.length); 69 | } 70 | 71 | @Override 72 | public void write(byte[] dataToWrite, int offset, int length) throws IOException { 73 | int numReadSoFar = 0; 74 | while (numReadSoFar < length) { 75 | int maxToRead = Math.min(buffer.length, length - numReadSoFar); 76 | int numReadThisLoop = expectedBytesStream.read(buffer, 0, maxToRead); 77 | if (numReadThisLoop == -1) { 78 | throw new MismatchException("EOF reached in expectedBytesStream"); 79 | } 80 | for (int matchCount = 0; matchCount < numReadThisLoop; matchCount++) { 81 | if (buffer[matchCount] != dataToWrite[offset + numReadSoFar + matchCount]) { 82 | throw new MismatchException("Data does not match"); 83 | } 84 | } 85 | numReadSoFar += numReadThisLoop; 86 | } 87 | } 88 | 89 | @Override 90 | public void close() throws IOException { 91 | expectedBytesStream.close(); 92 | } 93 | 94 | /** 95 | * Expects the end-of-file to be reached in the associated {@link InputStream}. 96 | * @throws IOException if the end-of-file has not yet been reached in the associated 97 | * {@link InputStream} 98 | */ 99 | public void expectEof() throws IOException { 100 | if (expectedBytesStream.read() != -1) { 101 | throw new MismatchException("EOF not reached in expectedBytesStream"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/MinimalCentralDirectoryMetadata.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | /** 18 | * Trivial struct containing the critical data about the central directory: the number of entries 19 | * it contains, the position within the file at which it starts, and its length. 20 | */ 21 | class MinimalCentralDirectoryMetadata { 22 | /** 23 | * The number of entries in the central directory. 24 | */ 25 | private final int numEntriesInCentralDirectory; 26 | 27 | /** 28 | * The file offset of the first byte of the central directory. 29 | */ 30 | private final long offsetOfCentralDirectory; 31 | 32 | /** 33 | * The length of the central directory, in bytes. 34 | */ 35 | private final long lengthOfCentralDirectory; 36 | 37 | /** 38 | * Constructs a new metadata object with the specified values 39 | * @param numEntriesInCentralDirectory the number of entries in the central directory 40 | * @param offsetOfCentralDirectory the file offset of the first byte of the central directory 41 | * @param lengthOfCentralDirectory the length of the central directory, in bytes 42 | */ 43 | MinimalCentralDirectoryMetadata( 44 | int numEntriesInCentralDirectory, 45 | long offsetOfCentralDirectory, 46 | long lengthOfCentralDirectory) { 47 | this.numEntriesInCentralDirectory = numEntriesInCentralDirectory; 48 | this.offsetOfCentralDirectory = offsetOfCentralDirectory; 49 | this.lengthOfCentralDirectory = lengthOfCentralDirectory; 50 | } 51 | 52 | /** 53 | * Returns the number of entries in the central directory. 54 | * @return as described 55 | */ 56 | public final int getNumEntriesInCentralDirectory() { 57 | return numEntriesInCentralDirectory; 58 | } 59 | 60 | /** 61 | * Returns the file offset of the first byte of the central directory. 62 | * @return as described 63 | */ 64 | public final long getOffsetOfCentralDirectory() { 65 | return offsetOfCentralDirectory; 66 | } 67 | 68 | /** 69 | * Returns the length of the central directory, in bytes. 70 | * @return as described 71 | */ 72 | public final long getLengthOfCentralDirectory() { 73 | return lengthOfCentralDirectory; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/MinimalZipArchive.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.shared.RandomAccessFileInputStream; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | import java.util.Collections; 23 | import java.util.Comparator; 24 | import java.util.List; 25 | import java.util.zip.ZipException; 26 | 27 | /** 28 | * A simplified, structural representation of a zip or zip-like (jar, apk, etc) archive. The class 29 | * provides the minimum structural information needed for patch generation and is not suitable as a 30 | * general zip-processing library. In particular, there is little or no verification that any of the 31 | * zip structure is correct or sane; it assumed that the input is sane. 32 | */ 33 | public class MinimalZipArchive { 34 | 35 | /** 36 | * Sorts {@link MinimalZipEntry} objects by {@link MinimalZipEntry#getFileOffsetOfLocalEntry()} in 37 | * ascending order. 38 | */ 39 | private static final Comparator LOCAL_ENTRY_OFFSET_COMAPRATOR = 40 | new Comparator() { 41 | @Override 42 | public int compare(MinimalZipEntry o1, MinimalZipEntry o2) { 43 | return Long.compare(o1.getFileOffsetOfLocalEntry(), o2.getFileOffsetOfLocalEntry()); 44 | } 45 | }; 46 | 47 | /** 48 | * Generate a listing of all of the files in a zip archive in file order and return it. Each entry 49 | * is a {@link MinimalZipEntry}, which has just enough information to generate a patch. 50 | * @param file the zip file to read 51 | * @return such a listing 52 | * @throws IOException if anything goes wrong while reading 53 | */ 54 | public static List listEntries(File file) throws IOException { 55 | try (RandomAccessFileInputStream in = new RandomAccessFileInputStream(file)) { 56 | return listEntriesInternal(in); 57 | } 58 | } 59 | 60 | /** 61 | * Internal implementation of {@link #listEntries(File)}. 62 | * @param in the input stream to read from 63 | * @return see {@link #listEntries(File)} 64 | * @throws IOException if anything goes wrong while reading 65 | */ 66 | private static List listEntriesInternal(RandomAccessFileInputStream in) 67 | throws IOException { 68 | // Step 1: Locate the end-of-central-directory record header. 69 | long offsetOfEocd = MinimalZipParser.locateStartOfEocd(in, 32768); 70 | if (offsetOfEocd == -1) { 71 | // Archive is weird, abort. 72 | throw new ZipException("EOCD record not found in last 32k of archive, giving up"); 73 | } 74 | 75 | // Step 2: Parse the end-of-central-directory data to locate the central directory itself 76 | in.setRange(offsetOfEocd, in.length() - offsetOfEocd); 77 | MinimalCentralDirectoryMetadata centralDirectoryMetadata = MinimalZipParser.parseEocd(in); 78 | 79 | // Step 3: Extract a list of all central directory entries (contiguous data stream) 80 | in.setRange( 81 | centralDirectoryMetadata.getOffsetOfCentralDirectory(), 82 | centralDirectoryMetadata.getLengthOfCentralDirectory()); 83 | List minimalZipEntries = 84 | new ArrayList(centralDirectoryMetadata.getNumEntriesInCentralDirectory()); 85 | for (int x = 0; x < centralDirectoryMetadata.getNumEntriesInCentralDirectory(); x++) { 86 | minimalZipEntries.add(MinimalZipParser.parseCentralDirectoryEntry(in)); 87 | } 88 | 89 | // Step 4: Sort the entries in file order, not central directory order. 90 | Collections.sort(minimalZipEntries, LOCAL_ENTRY_OFFSET_COMAPRATOR); 91 | 92 | // Step 5: Seek out each local entry and calculate the offset of the compressed data within 93 | for (int x = 0; x < minimalZipEntries.size(); x++) { 94 | MinimalZipEntry entry = minimalZipEntries.get(x); 95 | long offsetOfNextEntry; 96 | if (x < minimalZipEntries.size() - 1) { 97 | // Don't allow reading past the start of the next entry, for sanity. 98 | offsetOfNextEntry = minimalZipEntries.get(x + 1).getFileOffsetOfLocalEntry(); 99 | } else { 100 | // Last entry. Don't allow reading into the central directory, for sanity. 101 | offsetOfNextEntry = centralDirectoryMetadata.getOffsetOfCentralDirectory(); 102 | } 103 | long rangeLength = offsetOfNextEntry - entry.getFileOffsetOfLocalEntry(); 104 | in.setRange(entry.getFileOffsetOfLocalEntry(), rangeLength); 105 | long relativeDataOffset = MinimalZipParser.parseLocalEntryAndGetCompressedDataOffset(in); 106 | entry.setFileOffsetOfCompressedData(entry.getFileOffsetOfLocalEntry() + relativeDataOffset); 107 | } 108 | 109 | // Done! 110 | return minimalZipEntries; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/MismatchException.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * Thrown when data does not match expected values. 21 | */ 22 | @SuppressWarnings("serial") 23 | public class MismatchException extends IOException { 24 | /** 25 | * Construct an exception with the specified message 26 | * @param message the message 27 | */ 28 | public MismatchException(String message) { 29 | super(message); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/PatchWriter.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.shared.JreDeflateParameters; 18 | import com.google.archivepatcher.shared.PatchConstants; 19 | import com.google.archivepatcher.shared.TypedRange; 20 | import java.io.BufferedInputStream; 21 | import java.io.DataOutputStream; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.IOException; 25 | import java.io.OutputStream; 26 | 27 | /** 28 | * Writes patches. 29 | */ 30 | public class PatchWriter { 31 | /** 32 | * The patch plan. 33 | */ 34 | private final PreDiffPlan plan; 35 | 36 | /** 37 | * The expected size of the delta-friendly old file, provided as a convenience for the patch 38 | * applier to reserve space on the filesystem for applying the patch. 39 | */ 40 | private final long deltaFriendlyOldFileSize; 41 | 42 | /** 43 | * The expected size of the delta-friendly new file, provided for forward compatibility. 44 | */ 45 | private final long deltaFriendlyNewFileSize; 46 | 47 | /** 48 | * The delta that transforms the old delta-friendly file into the new delta-friendly file. 49 | */ 50 | private final File deltaFile; 51 | 52 | /** 53 | * Creates a new patch writer. 54 | * 55 | * @param plan the patch plan 56 | * @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, provided as a 57 | * convenience for the patch applier to reserve space on the filesystem for 58 | * applying the patch 59 | * @param deltaFriendlyNewFileSize the expected size of the delta-friendly new file, provided for 60 | * forward compatibility 61 | * @param deltaFile the delta that transforms the old delta-friendly file into the new 62 | * delta-friendly file 63 | */ 64 | public PatchWriter( 65 | PreDiffPlan plan, 66 | long deltaFriendlyOldFileSize, 67 | long deltaFriendlyNewFileSize, 68 | File deltaFile) { 69 | this.plan = plan; 70 | this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize; 71 | this.deltaFriendlyNewFileSize = deltaFriendlyNewFileSize; 72 | this.deltaFile = deltaFile; 73 | } 74 | 75 | /** 76 | * Write a v1-style patch to the specified output stream. 77 | * @param out the stream to write the patch to 78 | * @throws IOException if anything goes wrong 79 | */ 80 | public void writeV1Patch(OutputStream out) throws IOException { 81 | // Use DataOutputStream for ease of writing. This is deliberately left open, as closing it would 82 | // close the output stream that was passed in and that is not part of the method's documented 83 | // behavior. 84 | @SuppressWarnings("resource") 85 | DataOutputStream dataOut = new DataOutputStream(out); 86 | 87 | dataOut.write(PatchConstants.IDENTIFIER.getBytes("US-ASCII")); 88 | dataOut.writeInt(0); // Flags (reserved) 89 | dataOut.writeLong(deltaFriendlyOldFileSize); 90 | 91 | // Write out all the delta-friendly old file uncompression instructions 92 | dataOut.writeInt(plan.getOldFileUncompressionPlan().size()); 93 | for (TypedRange range : plan.getOldFileUncompressionPlan()) { 94 | dataOut.writeLong(range.getOffset()); 95 | dataOut.writeLong(range.getLength()); 96 | } 97 | 98 | // Write out all the delta-friendly new file recompression instructions 99 | dataOut.writeInt(plan.getDeltaFriendlyNewFileRecompressionPlan().size()); 100 | for (TypedRange range : plan.getDeltaFriendlyNewFileRecompressionPlan()) { 101 | dataOut.writeLong(range.getOffset()); 102 | dataOut.writeLong(range.getLength()); 103 | // Write the deflate information 104 | dataOut.write(PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue); 105 | dataOut.write(range.getMetadata().level); 106 | dataOut.write(range.getMetadata().strategy); 107 | dataOut.write(range.getMetadata().nowrap ? 1 : 0); 108 | } 109 | 110 | // Now the delta section 111 | // First write the number of deltas present in the patch. In v1, there is always exactly one 112 | // delta, and it is for the entire input; in future versions there may be multiple deltas, of 113 | // arbitrary types. 114 | dataOut.writeInt(1); 115 | // In v1 the delta format is always bsdiff, so write it unconditionally. 116 | dataOut.write(PatchConstants.DeltaFormat.BSDIFF.patchValue); 117 | 118 | // Write the working ranges. In v1 these are always the entire contents of the delta-friendly 119 | // old file and the delta-friendly new file. These are for forward compatibility with future 120 | // versions that may allow deltas of arbitrary formats to be mapped to arbitrary ranges. 121 | dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly old file 122 | dataOut.writeLong(deltaFriendlyOldFileSize); // i.e., length of the working range in old 123 | dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly new file 124 | dataOut.writeLong(deltaFriendlyNewFileSize); // i.e., length of the working range in new 125 | 126 | // Finally, the length of the delta and the delta itself. 127 | dataOut.writeLong(deltaFile.length()); 128 | try (FileInputStream deltaFileIn = new FileInputStream(deltaFile); 129 | BufferedInputStream deltaIn = new BufferedInputStream(deltaFileIn)) { 130 | byte[] buffer = new byte[32768]; 131 | int numRead = 0; 132 | while ((numRead = deltaIn.read(buffer)) >= 0) { 133 | dataOut.write(buffer, 0, numRead); 134 | } 135 | } 136 | dataOut.flush(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/QualifiedRecommendation.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | /** 18 | * A fully qualified recommendation, consisting of an {@link MinimalZipEntry} from the old file, 19 | * a {@link MinimalZipEntry} from the new file, a {@link Recommendation} for how to proceed and a 20 | * {@link RecommendationReason} for that recommendation. 21 | */ 22 | public class QualifiedRecommendation { 23 | /** 24 | * The entry in the old file. 25 | */ 26 | private final MinimalZipEntry oldEntry; 27 | 28 | /** 29 | * The entry in the new file. 30 | */ 31 | private final MinimalZipEntry newEntry; 32 | 33 | /** 34 | * The recommendation for how to proceed on the pair of entries. 35 | */ 36 | private final Recommendation recommendation; 37 | 38 | /** 39 | * The reason for the recommendation. 40 | */ 41 | private final RecommendationReason reason; 42 | 43 | /** 44 | * Construct a new qualified recommendation with the specified data. 45 | * @param oldEntry the entry in the old file 46 | * @param newEntry the entry in the new file 47 | * @param recommendation the recommendation for this tuple of entries 48 | * @param reason the reason for the recommendation 49 | */ 50 | public QualifiedRecommendation( 51 | MinimalZipEntry oldEntry, 52 | MinimalZipEntry newEntry, 53 | Recommendation recommendation, 54 | RecommendationReason reason) { 55 | super(); 56 | this.oldEntry = oldEntry; 57 | this.newEntry = newEntry; 58 | this.recommendation = recommendation; 59 | this.reason = reason; 60 | } 61 | 62 | /** 63 | * Returns the entry in the old file. 64 | * @return as described 65 | */ 66 | public MinimalZipEntry getOldEntry() { 67 | return oldEntry; 68 | } 69 | 70 | /** 71 | * Returns the entry in the new file. 72 | * @return as described 73 | */ 74 | public MinimalZipEntry getNewEntry() { 75 | return newEntry; 76 | } 77 | 78 | /** 79 | * Returns the recommendation for how to proceed for this tuple of entries. 80 | * @return as described 81 | */ 82 | public Recommendation getRecommendation() { 83 | return recommendation; 84 | } 85 | 86 | /** 87 | * Returns the reason for the recommendation. 88 | * @return as described 89 | */ 90 | public RecommendationReason getReason() { 91 | return reason; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | final int prime = 31; 97 | int result = 1; 98 | result = prime * result + ((newEntry == null) ? 0 : newEntry.hashCode()); 99 | result = prime * result + ((oldEntry == null) ? 0 : oldEntry.hashCode()); 100 | result = prime * result + ((reason == null) ? 0 : reason.hashCode()); 101 | result = prime * result + ((recommendation == null) ? 0 : recommendation.hashCode()); 102 | return result; 103 | } 104 | 105 | @Override 106 | public boolean equals(Object obj) { 107 | if (this == obj) { 108 | return true; 109 | } 110 | if (obj == null) { 111 | return false; 112 | } 113 | if (getClass() != obj.getClass()) { 114 | return false; 115 | } 116 | QualifiedRecommendation other = (QualifiedRecommendation) obj; 117 | if (newEntry == null) { 118 | if (other.newEntry != null) { 119 | return false; 120 | } 121 | } else if (!newEntry.equals(other.newEntry)) { 122 | return false; 123 | } 124 | if (oldEntry == null) { 125 | if (other.oldEntry != null) { 126 | return false; 127 | } 128 | } else if (!oldEntry.equals(other.oldEntry)) { 129 | return false; 130 | } 131 | if (reason != other.reason) { 132 | return false; 133 | } 134 | if (recommendation != other.recommendation) { 135 | return false; 136 | } 137 | return true; 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "QualifiedRecommendation [oldEntry=" 143 | + oldEntry.getFileName() 144 | + ", newEntry=" 145 | + newEntry.getFileName() 146 | + ", recommendation=" 147 | + recommendation 148 | + ", reason=" 149 | + reason 150 | + "]"; 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/Recommendation.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | /** 18 | * Recommendations for how to uncompress entries in old and new archives. 19 | */ 20 | public enum Recommendation { 21 | 22 | /** 23 | * Uncompress only the old entry. 24 | */ 25 | UNCOMPRESS_OLD(true, false), 26 | 27 | /** 28 | * Uncompress only the new entry. 29 | */ 30 | UNCOMPRESS_NEW(false, true), 31 | 32 | /** 33 | * Uncompress both the old and new entries. 34 | */ 35 | UNCOMPRESS_BOTH(true, true), 36 | 37 | /** 38 | * Uncompress neither entry. 39 | */ 40 | UNCOMPRESS_NEITHER(false, false); 41 | 42 | /** 43 | * True if the old entry should be uncompressed. 44 | */ 45 | public final boolean uncompressOldEntry; 46 | 47 | /** 48 | * True if the new entry should be uncompressed. 49 | */ 50 | public final boolean uncompressNewEntry; 51 | 52 | /** 53 | * Constructs a new recommendation with the specified behaviors. 54 | * @param uncompressOldEntry true if the old entry should be uncompressed 55 | * @param uncompressNewEntry true if the new entry should be uncompressed 56 | */ 57 | private Recommendation(boolean uncompressOldEntry, boolean uncompressNewEntry) { 58 | this.uncompressOldEntry = uncompressOldEntry; 59 | this.uncompressNewEntry = uncompressNewEntry; 60 | } 61 | } -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/RecommendationModifier.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.File; 18 | import java.util.List; 19 | 20 | /** 21 | * Provides a mechanism to review and possibly modify the {@link QualifiedRecommendation}s that will 22 | * be used to derive a {@link PreDiffPlan}. 23 | */ 24 | public interface RecommendationModifier { 25 | /** 26 | * Given a list of {@link QualifiedRecommendation} objects, returns a list of the same type that 27 | * has been arbitrarily adjusted as desired by the implementation. Implementations must return a 28 | * list of recommendations that contains the same tuples of (oldEntry, newEntry) but may change 29 | * the results of {@link QualifiedRecommendation#getRecommendation()} and {@link 30 | * QualifiedRecommendation#getReason()} to any sane values. 31 | * 32 | * @param oldFile the old file that is being diffed 33 | * @param newFile the new file that is being diffed 34 | * @param originalRecommendations the original recommendations 35 | * @return the updated list of recommendations 36 | */ 37 | public List getModifiedRecommendations( 38 | File oldFile, File newFile, List originalRecommendations); 39 | } 40 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/RecommendationReason.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | /** 18 | * Reasons for a corresponding {@link Recommendation}. 19 | */ 20 | public enum RecommendationReason { 21 | /** 22 | * The entry in the new file is compressed using deflate in a way that cannot be reliably 23 | * reproduced. This could be caused by using an unsupported version of zlib. 24 | */ 25 | DEFLATE_UNSUITABLE, 26 | /** 27 | * The entry in the new file is compressed in a way that cannot be reliably reproduced (or one of 28 | * the entries is compressed using something other than deflate, but this is very uncommon). 29 | */ 30 | UNSUITABLE, 31 | 32 | /** 33 | * Both the old and new entries are already uncompressed. 34 | */ 35 | BOTH_ENTRIES_UNCOMPRESSED, 36 | 37 | /** 38 | * An entry that was uncompressed in the old file is compressed in the new file. 39 | */ 40 | UNCOMPRESSED_CHANGED_TO_COMPRESSED, 41 | 42 | /** 43 | * An entry that was compressed in the old file is uncompressed in the new file. 44 | */ 45 | COMPRESSED_CHANGED_TO_UNCOMPRESSED, 46 | 47 | /** 48 | * The compressed bytes in the old file do not match the compressed bytes in the new file. 49 | */ 50 | COMPRESSED_BYTES_CHANGED, 51 | 52 | /** The compressed bytes in the old file are identical to the compressed bytes in the new file. */ 53 | COMPRESSED_BYTES_IDENTICAL, 54 | 55 | /** 56 | * A resource constraint prohibits touching the old entry, the new entry, or both. For example, 57 | * there may be a limit on the total amount of temp space that will be available for applying a 58 | * patch or a limit on the total amount of CPU time that can be expended on recompression when 59 | * applying a patch, etc. 60 | */ 61 | RESOURCE_CONSTRAINED; 62 | } 63 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/TempFileHolder.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.io.Closeable; 18 | import java.io.File; 19 | import java.io.IOException; 20 | 21 | /** 22 | * A closeable container for a temp file that deletes itself on {@link #close()}. This is convenient 23 | * for try-with-resources constructs that need to use temp files in scope. 24 | */ 25 | public class TempFileHolder implements Closeable { 26 | /** 27 | * The file that is wrapped by this holder. 28 | */ 29 | public final File file; 30 | 31 | /** 32 | * Create a new temp file and wrap it in an instance of this class. The file is automatically 33 | * scheduled for deletion on JVM termination, so it is a serious error to rely on this file path 34 | * being a durable artifact. 35 | * @throws IOException if unable to create the file 36 | */ 37 | public TempFileHolder() throws IOException { 38 | file = File.createTempFile("archive_patcher", "tmp"); 39 | file.deleteOnExit(); 40 | } 41 | 42 | @Override 43 | public void close() throws IOException { 44 | file.delete(); 45 | } 46 | } -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/bsdiff/BsDiffDeltaGenerator.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import com.google.archivepatcher.generator.DeltaGenerator; 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.io.OutputStream; 21 | 22 | /** 23 | * An implementation of {@link DeltaGenerator} that uses {@link BsDiffPatchWriter} to write a 24 | * bsdiff patch that represents the delta between given inputs. 25 | */ 26 | public class BsDiffDeltaGenerator implements DeltaGenerator { 27 | /** 28 | * The minimum match length to use for bsdiff. 29 | */ 30 | private static final int MATCH_LENGTH_BYTES = 16; 31 | 32 | @Override 33 | public void generateDelta(File oldBlob, File newBlob, OutputStream deltaOut) 34 | throws IOException, InterruptedException { 35 | BsDiffPatchWriter.generatePatch(oldBlob, newBlob, deltaOut, MATCH_LENGTH_BYTES); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/bsdiff/BsUtil.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * Utility functions to be shared between BsDiff and BsPatch. 23 | */ 24 | class BsUtil { 25 | /** 26 | * Mask to determine whether a long written by {@link #writeFormattedLong(long, OutputStream)} 27 | * is negative. 28 | */ 29 | private static final long NEGATIVE_MASK = 1L << 63; 30 | 31 | /** 32 | * Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant 33 | * byte is written first and the most significant byte is written last. 34 | * @param value the value to write 35 | * @param outputStream the stream to write to 36 | */ 37 | static void writeFormattedLong(final long value, OutputStream outputStream) 38 | throws IOException { 39 | long y = value; 40 | if (y < 0) { 41 | y = (-y) | NEGATIVE_MASK; 42 | } 43 | 44 | for (int i = 0; i < 8; ++i) { 45 | outputStream.write((byte) (y & 0xff)); 46 | y >>>= 8; 47 | } 48 | } 49 | 50 | /** 51 | * Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from 52 | * the specified {@link InputStream}. 53 | * @param inputStream the stream to read from 54 | */ 55 | static long readFormattedLong(InputStream inputStream) throws IOException { 56 | long result = 0; 57 | for (int bitshift = 0; bitshift < 64; bitshift += 8) { 58 | result |= ((long) inputStream.read()) << bitshift; 59 | } 60 | 61 | if ((result - NEGATIVE_MASK) > 0) { 62 | result = (result & ~NEGATIVE_MASK) * -1; 63 | } 64 | return result; 65 | } 66 | 67 | /** 68 | * Provides functional equivalent to C/C++ lexicographical_compare. Warning: this calls {@link 69 | * RandomAccessObject#seek(long)}, so the internal state of the data objects will be modified. 70 | * 71 | * @param data1 first byte array 72 | * @param start1 index in the first array at which to start comparing 73 | * @param length1 length of first byte array 74 | * @param data2 second byte array 75 | * @param start2 index in the second array at which to start comparing 76 | * @param length2 length of second byte array 77 | * @return result of lexicographical compare: negative if the first difference has a lower value 78 | * in the first array, positive if the first difference has a lower value in the second array. 79 | * If both arrays compare equal until one of them ends, the shorter sequence is 80 | * lexicographically less than the longer one (i.e., it returns len(first array) - 81 | * len(second array)). 82 | */ 83 | static int lexicographicalCompare( 84 | final RandomAccessObject data1, 85 | final int start1, 86 | final int length1, 87 | final RandomAccessObject data2, 88 | final int start2, 89 | final int length2) 90 | throws IOException { 91 | int bytesLeft = Math.min(length1, length2); 92 | 93 | data1.seek(start1); 94 | data2.seek(start2); 95 | while (bytesLeft-- > 0) { 96 | final int i1 = data1.readUnsignedByte(); 97 | final int i2 = data2.readUnsignedByte(); 98 | 99 | if (i1 != i2) { 100 | return i1 - i2; 101 | } 102 | } 103 | 104 | return length1 - length2; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/bsdiff/Matcher.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * Helper class which iterates through |newData| finding the longest valid exact matches between 21 | * |newData| and |oldData|. The interface exists for the sake of testing. 22 | */ 23 | interface Matcher { 24 | /** 25 | * Determine the range for the next match, and store it in member state. 26 | * @return a {@link NextMatch} describing the result 27 | */ 28 | NextMatch next() throws IOException, InterruptedException; 29 | 30 | /** 31 | * Contains a boolean which indicates whether a match was found, the old position (if a match was 32 | * found), and the new position (if a match was found). 33 | */ 34 | static class NextMatch { 35 | final boolean didFindMatch; 36 | final int oldPosition; 37 | final int newPosition; 38 | 39 | static NextMatch of(boolean didFindMatch, int oldPosition, int newPosition) { 40 | return new NextMatch(didFindMatch, oldPosition, newPosition); 41 | } 42 | 43 | private NextMatch(boolean didFindMatch, int oldPosition, int newPosition) { 44 | this.didFindMatch = didFindMatch; 45 | this.oldPosition = oldPosition; 46 | this.newPosition = newPosition; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/bsdiff/RandomAccessObjectFactory.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessByteArrayObject; 18 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessFileObject; 19 | import com.google.archivepatcher.generator.bsdiff.RandomAccessObject.RandomAccessMmapObject; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.io.RandomAccessFile; 24 | 25 | /** 26 | * A factory for creating instances of {@link RandomAccessObject}. BsDiff needs to store some 27 | * ancillary data of size proportional to the binary to be patched. This allows abstraction of the 28 | * allocation so that BsDiff can run either entirely in-memory (faster) or with file-backed swap 29 | * (handles bigger inputs without consuming inordinate amounts of memory). 30 | */ 31 | public interface RandomAccessObjectFactory { 32 | public RandomAccessObject create(int size) throws IOException; 33 | 34 | /** 35 | * A factory that produces {@link RandomAccessFileObject} instances backed by temp files. 36 | */ 37 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't 38 | // really be the responsibility of RandomAccessObject. 39 | public static final class RandomAccessFileObjectFactory implements RandomAccessObjectFactory { 40 | private static final String FILE_NAME_PREFIX = "wavsprafof"; 41 | private final String mMode; 42 | 43 | /** 44 | * Factory for a RandomAccessFileObject. 45 | * @param mode the file mode string ("r", "w", "rw", etc - see documentation for 46 | * {@link RandomAccessFile}) 47 | */ 48 | public RandomAccessFileObjectFactory(String mode) { 49 | mMode = mode; 50 | } 51 | 52 | /** 53 | * Creates a temp file, and returns a {@link RandomAccessFile} wrapped in a 54 | * {@link RandomAccessFileObject} representing the new temp file. The temp file does not need to 55 | * explicitly be managed (deleted) by the caller, as long as the caller ensures 56 | * {@link RandomAccessObject#close()} is called when the object is no longer needed. 57 | */ 58 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't 59 | // really be the responsibility of RandomAccessObject. 60 | @Override 61 | public RandomAccessObject create(int size) throws IOException { 62 | return new RandomAccessObject.RandomAccessFileObject( 63 | File.createTempFile(FILE_NAME_PREFIX, "temp"), mMode, true); 64 | } 65 | } 66 | 67 | /** 68 | * A factory that produces {@link RandomAccessByteArrayObject} instances backed by memory. 69 | */ 70 | public static final class RandomAccessByteArrayObjectFactory 71 | implements RandomAccessObjectFactory { 72 | @Override 73 | public RandomAccessObject create(int size) { 74 | return new RandomAccessObject.RandomAccessByteArrayObject(size); 75 | } 76 | } 77 | 78 | /** 79 | * A factory that produces {@link RandomAccessMmapObject} instances backed by temp files.. 80 | */ 81 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't 82 | // really be the responsibility of RandomAccessObject. 83 | public static final class RandomAccessMmapObjectFactory implements RandomAccessObjectFactory { 84 | private static final String FILE_NAME_PREFIX = "wavsprafof"; 85 | private String mMode; 86 | 87 | /** 88 | * Factory for a RandomAccessMmapObject. 89 | * @param mode the file mode string ("r", "w", "rw", etc - see documentation for 90 | * {@link RandomAccessFile}) 91 | */ 92 | public RandomAccessMmapObjectFactory(String mode) { 93 | mMode = mode; 94 | } 95 | 96 | /** 97 | * Creates a temp file, and returns a {@link RandomAccessFile} wrapped in a 98 | * {@link RandomAccessMmapObject} representing the new temp file. The temp file does not need to 99 | * explicitly be managed (deleted) by the caller, as long as the caller ensures 100 | * {@link RandomAccessObject#close()} is called when the object is no longer needed. 101 | */ 102 | // TODO(hartmanng): rethink the handling of these temp files. It's confusing and shouldn't 103 | // really be the responsibility of RandomAccessObject. 104 | @Override 105 | public RandomAccessObject create(int size) throws IOException { 106 | return new RandomAccessObject.RandomAccessMmapObject(FILE_NAME_PREFIX, mMode, size); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/bsdiff/SuffixSorter.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * An algorithm that performs a suffix sort on a given input and returns a suffix array. 21 | * See https://en.wikipedia.org/wiki/Suffix_array 22 | */ 23 | public interface SuffixSorter { 24 | 25 | /** 26 | * Perform a "suffix sort". Note: the returned {@link RandomAccessObject} should be closed by the 27 | * caller. 28 | * 29 | * @param data the data to sort 30 | * @return the suffix array, as a {@link RandomAccessObject} 31 | * @throws IOException if unable to read data 32 | * @throws InterruptedException if any thread interrupts this thread 33 | */ 34 | RandomAccessObject suffixSort(RandomAccessObject data) throws IOException, InterruptedException; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/similarity/Crc32SimilarityFinder.java: -------------------------------------------------------------------------------- 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.generator.similarity; 16 | 17 | import com.google.archivepatcher.generator.MinimalZipEntry; 18 | 19 | import java.io.File; 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.LinkedList; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | /** 28 | * Detects identical files on the basis of the CRC32 of uncompressed content. All entries that have 29 | * the same CRC32 will be identified as similar (and presumably are identical, in the absence of 30 | * hash collisions). 31 | */ 32 | public class Crc32SimilarityFinder extends SimilarityFinder { 33 | 34 | /** 35 | * All entries in the base archive, organized by CRC32. 36 | */ 37 | private final Map> baseEntriesByCrc32 = new HashMap<>(); 38 | 39 | /** 40 | * Constructs a new similarity finder with the specified parameters. 41 | * @param baseArchive the base archive that contains the entries to be searched 42 | * @param baseEntries the entries in the base archive that are eligible to be searched 43 | */ 44 | public Crc32SimilarityFinder(File baseArchive, Collection baseEntries) { 45 | super(baseArchive, baseEntries); 46 | for (MinimalZipEntry oldEntry : baseEntries) { 47 | long crc32 = oldEntry.getCrc32OfUncompressedData(); 48 | List entriesForCrc32 = baseEntriesByCrc32.get(crc32); 49 | if (entriesForCrc32 == null) { 50 | entriesForCrc32 = new LinkedList<>(); 51 | baseEntriesByCrc32.put(crc32, entriesForCrc32); 52 | } 53 | entriesForCrc32.add(oldEntry); 54 | } 55 | } 56 | 57 | @Override 58 | public List findSimilarFiles(File newArchive, MinimalZipEntry newEntry) { 59 | List matchedEntries = 60 | baseEntriesByCrc32.get(newEntry.getCrc32OfUncompressedData()); 61 | if (matchedEntries == null) { 62 | return Collections.emptyList(); 63 | } 64 | return Collections.unmodifiableList(matchedEntries); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /generator/src/main/java/com/google/archivepatcher/generator/similarity/SimilarityFinder.java: -------------------------------------------------------------------------------- 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.generator.similarity; 16 | 17 | import java.io.File; 18 | import java.util.Collection; 19 | import java.util.List; 20 | 21 | import com.google.archivepatcher.generator.MinimalZipEntry; 22 | 23 | /** 24 | * A class that analyzes an archive to find files similar to a specified file. 25 | */ 26 | public abstract class SimilarityFinder { 27 | 28 | /** 29 | * The base archive that contains the entries to be searched. 30 | */ 31 | protected final File baseArchive; 32 | 33 | /** 34 | * The entries in the base archive that are eligible to be searched. 35 | */ 36 | protected final Collection baseEntries; 37 | 38 | /** 39 | * Create a new instance to check for similarity of arbitrary files against the specified entries 40 | * in the specified archive. 41 | * @param baseArchive the base archive that contains the entries to be scored against 42 | * @param baseEntries the entries in the base archive that are eligible to be scored against. 43 | */ 44 | public SimilarityFinder(File baseArchive, Collection baseEntries) { 45 | this.baseArchive = baseArchive; 46 | this.baseEntries = baseEntries; 47 | } 48 | 49 | /** 50 | * Searches for files similar to the specified entry in the specified new archive against all of 51 | * the available entries in the base archive. 52 | * @param newArchive the new archive that contains the new entry 53 | * @param newEntry the new entry to compare against the entries in the base archive 54 | * @return a {@link List} of {@link MinimalZipEntry} entries (possibly empty but never null) from 55 | * the base archive that are similar to the new archive; if the list has more than one entry, the 56 | * entries should be in order from most similar to least similar. 57 | */ 58 | public abstract List findSimilarFiles(File newArchive, MinimalZipEntry newEntry); 59 | } 60 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/ByteArrayHolderTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | /** 23 | * Tests for {@link ByteArrayHolder}. 24 | */ 25 | @RunWith(JUnit4.class) 26 | @SuppressWarnings("javadoc") 27 | public class ByteArrayHolderTest { 28 | 29 | @Test 30 | public void testGetters() { 31 | byte[] data = "hello world".getBytes(); 32 | ByteArrayHolder byteArrayHolder = new ByteArrayHolder(data); 33 | Assert.assertSame(data, byteArrayHolder.getData()); 34 | } 35 | 36 | @Test 37 | public void testHashCode() { 38 | byte[] data1a = "hello world".getBytes(); 39 | byte[] data1b = new String("hello world").getBytes(); 40 | byte[] data2 = "hello another world".getBytes(); 41 | ByteArrayHolder rawText1a = new ByteArrayHolder(data1a); 42 | ByteArrayHolder rawText1b = new ByteArrayHolder(data1b); 43 | Assert.assertEquals(rawText1a.hashCode(), rawText1b.hashCode()); 44 | ByteArrayHolder rawText2 = new ByteArrayHolder(data2); 45 | Assert.assertNotEquals(rawText1a.hashCode(), rawText2.hashCode()); 46 | ByteArrayHolder rawText3 = new ByteArrayHolder(null); 47 | Assert.assertNotEquals(rawText1a.hashCode(), rawText3.hashCode()); 48 | Assert.assertNotEquals(rawText2.hashCode(), rawText3.hashCode()); 49 | } 50 | 51 | @Test 52 | public void testEquals() { 53 | byte[] data1a = "hello world".getBytes(); 54 | byte[] data1b = new String("hello world").getBytes(); 55 | byte[] data2 = "hello another world".getBytes(); 56 | ByteArrayHolder rawText1a = new ByteArrayHolder(data1a); 57 | Assert.assertEquals(rawText1a, rawText1a); 58 | ByteArrayHolder rawText1b = new ByteArrayHolder(data1b); 59 | Assert.assertEquals(rawText1a, rawText1b); 60 | ByteArrayHolder rawText2 = new ByteArrayHolder(data2); 61 | Assert.assertNotEquals(rawText1a, rawText2); 62 | ByteArrayHolder rawText3 = new ByteArrayHolder(null); 63 | Assert.assertNotEquals(rawText1a, rawText3); 64 | Assert.assertNotEquals(rawText3, rawText1a); 65 | Assert.assertNotEquals(rawText1a, 42); 66 | Assert.assertNotEquals(rawText1a, null); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/FileByFileV1DeltaGeneratorTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.shared.UnitTestZipArchive; 18 | import java.io.ByteArrayOutputStream; 19 | import org.junit.Assert; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | /** 25 | * Tests for {@link FileByFileV1DeltaGenerator}. This relies heavily on the correctness of {@link 26 | * PatchWriterTest}, which validates the patch writing process itself, {@link PreDiffPlannerTest}, 27 | * which validates the decision making process for delta-friendly blobs, and {@link 28 | * PreDiffExecutorTest}, which validates the ability to create the delta-friendly blobs. The {@link 29 | * FileByFileV1DeltaGenerator} itself is relatively simple, combining all of these pieces 30 | * of functionality together to create a patch; so the tests here are just ensuring that a patch can 31 | * be produced. 32 | */ 33 | @RunWith(JUnit4.class) 34 | @SuppressWarnings("javadoc") 35 | public class FileByFileV1DeltaGeneratorTest { 36 | 37 | @Test 38 | public void testGenerateDelta_BaseCase() throws Exception { 39 | // Simple test of generating a patch with no changes. 40 | FileByFileV1DeltaGenerator generator = new FileByFileV1DeltaGenerator(); 41 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 42 | try (TempFileHolder oldArchive = new TempFileHolder(); 43 | TempFileHolder newArchive = new TempFileHolder()) { 44 | UnitTestZipArchive.saveTestZip(oldArchive.file); 45 | UnitTestZipArchive.saveTestZip(newArchive.file); 46 | generator.generateDelta(oldArchive.file, newArchive.file, buffer); 47 | } 48 | byte[] result = buffer.toByteArray(); 49 | Assert.assertTrue(result.length > 0); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/MatchingOutputStreamTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.IOException; 24 | 25 | /** 26 | * Tests for {@link MatchingOutputStream}. 27 | */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class MatchingOutputStreamTest { 31 | /** 32 | * The data to write to the stream. 33 | */ 34 | private byte[] data; 35 | 36 | /** 37 | * Input for matching. 38 | */ 39 | private ByteArrayInputStream inputStream; 40 | 41 | /** 42 | * The stream under test. 43 | */ 44 | private MatchingOutputStream outputStream; 45 | 46 | @Before 47 | public void setup() { 48 | data = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 49 | inputStream = new ByteArrayInputStream(data); 50 | outputStream = new MatchingOutputStream(inputStream, 3 /* buffer size */); 51 | } 52 | 53 | @Test 54 | public void testWrite_OneByte() throws IOException { 55 | for (int x = 0; x < data.length; x++) { 56 | outputStream.write(data[x] & 0xff); 57 | } 58 | } 59 | 60 | @Test 61 | public void testWrite_WholeBuffer() throws IOException { 62 | outputStream.write(data); 63 | } 64 | 65 | @Test 66 | public void testWrite_WholeBuffer_RealisticCopyBuffer() throws IOException { 67 | outputStream = new MatchingOutputStream(inputStream, 32768); // realistic copy buffer size 68 | outputStream.write(data); 69 | } 70 | 71 | @Test 72 | public void testWrite_PartialBuffer() throws IOException { 73 | for (int x = 0; x < data.length; x++) { 74 | outputStream.write(data, x, 1); 75 | } 76 | } 77 | 78 | @Test 79 | public void testExpectEof() throws IOException { 80 | outputStream.write(data); 81 | outputStream.expectEof(); 82 | } 83 | 84 | @Test(expected = MismatchException.class) 85 | public void testWrite_OneByte_MatchFail() throws IOException { 86 | outputStream.write(0); 87 | outputStream.write(77); 88 | } 89 | 90 | @Test(expected = MismatchException.class) 91 | public void testWrite_OneByte_StreamFail() throws IOException { 92 | // Write one byte more than the data match stream contains 93 | for (int x = 0; x <= data.length; x++) { 94 | outputStream.write(x); 95 | } 96 | } 97 | 98 | @Test(expected = MismatchException.class) 99 | public void testWrite_WholeBuffer_Fail() throws IOException { 100 | byte[] tweaked = new byte[] {0, 1, 2, 3, 4, 55, 6, 7, 8, 9}; 101 | outputStream.write(tweaked); 102 | } 103 | 104 | @Test(expected = MismatchException.class) 105 | public void testWrite_PartialBuffer_Fail() throws IOException { 106 | byte[] tweaked = new byte[] {0, 1, 2, 3, 4, 55, 6, 7, 8, 9}; 107 | outputStream.write(tweaked, 0, 8); 108 | } 109 | 110 | @Test(expected = MismatchException.class) 111 | public void testExpectEof_Fail() throws IOException { 112 | outputStream.write(data, 0, data.length - 1); 113 | outputStream.expectEof(); 114 | } 115 | 116 | @Test(expected = MismatchException.class) 117 | public void testWrite_PastEndOfMatchStream() throws IOException { 118 | outputStream.write(data); 119 | outputStream.write(data); 120 | } 121 | 122 | @SuppressWarnings({"resource", "unused"}) 123 | @Test(expected = IllegalArgumentException.class) 124 | public void testConstructor_BadMatchBufferLength() { 125 | new MatchingOutputStream(inputStream, 0); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/MinimalZipArchiveTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.shared.UnitTestZipArchive; 18 | import com.google.archivepatcher.shared.UnitTestZipEntry; 19 | 20 | import org.junit.After; 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.File; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.util.List; 31 | import java.util.zip.CRC32; 32 | 33 | /** 34 | * Tests for {@link MinimalZipParser}. 35 | */ 36 | @RunWith(JUnit4.class) 37 | @SuppressWarnings("javadoc") 38 | public class MinimalZipArchiveTest { 39 | private byte[] unitTestZipArchive; 40 | private File tempFile; 41 | 42 | @Before 43 | public void setup() throws Exception { 44 | unitTestZipArchive = UnitTestZipArchive.makeTestZip(); 45 | tempFile = File.createTempFile("MinimalZipArchiveTest", "zip"); 46 | tempFile.deleteOnExit(); 47 | try { 48 | FileOutputStream out = new FileOutputStream(tempFile); 49 | out.write(unitTestZipArchive); 50 | out.flush(); 51 | out.close(); 52 | } catch (IOException e) { 53 | try { 54 | tempFile.delete(); 55 | } catch (Exception ignored) { 56 | // Nothing 57 | } 58 | throw e; 59 | } 60 | } 61 | 62 | @After 63 | public void tearDown() { 64 | if (tempFile != null) { 65 | try { 66 | tempFile.delete(); 67 | } catch (Exception ignored) { 68 | // Nothing 69 | } 70 | } 71 | } 72 | 73 | @Test 74 | public void testListEntries() throws IOException { 75 | // Ensure all entries are found, and that they are in file order. 76 | List parsedEntries = MinimalZipArchive.listEntries(tempFile); 77 | long lastSeenHeaderOffset = -1; 78 | for (int x = 0; x < UnitTestZipArchive.allEntriesInFileOrder.size(); x++) { 79 | UnitTestZipEntry expected = UnitTestZipArchive.allEntriesInFileOrder.get(x); 80 | MinimalZipEntry actual = parsedEntries.get(x); 81 | Assert.assertEquals(expected.path, actual.getFileName()); 82 | Assert.assertEquals(expected.level == 0 ? 0 : 8, actual.getCompressionMethod()); 83 | Assert.assertEquals(expected.getCompressedBinaryContent().length, actual.getCompressedSize()); 84 | Assert.assertEquals( 85 | expected.getUncompressedBinaryContent().length, actual.getUncompressedSize()); 86 | Assert.assertEquals(false, actual.getGeneralPurposeFlagBit11()); 87 | CRC32 crc32 = new CRC32(); 88 | crc32.update(expected.getUncompressedBinaryContent()); 89 | Assert.assertEquals(crc32.getValue(), actual.getCrc32OfUncompressedData()); 90 | 91 | // Offset verification is a little trickier 92 | // 1. Verify that the offsets are in ascending order and increasing. 93 | Assert.assertTrue(actual.getFileOffsetOfLocalEntry() > lastSeenHeaderOffset); 94 | lastSeenHeaderOffset = actual.getFileOffsetOfLocalEntry(); 95 | 96 | // 2. Verify that the local signature header is at the calculated position 97 | byte[] expectedSignatureBlock = new byte[] {0x50, 0x4b, 0x03, 0x04}; 98 | for (int index = 0; index < 4; index++) { 99 | byte actualByte = unitTestZipArchive[((int) actual.getFileOffsetOfLocalEntry()) + index]; 100 | Assert.assertEquals(expectedSignatureBlock[index], actualByte); 101 | } 102 | 103 | // 3. Verify that the data is at the calculated position 104 | byte[] expectedContent = expected.getCompressedBinaryContent(); 105 | int calculatedDataOffset = (int) actual.getFileOffsetOfCompressedData(); 106 | for (int index = 0; index < expectedContent.length; index++) { 107 | Assert.assertEquals( 108 | expectedContent[index], unitTestZipArchive[calculatedDataOffset + index]); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/PreDiffPlanTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import com.google.archivepatcher.shared.JreDeflateParameters; 18 | import com.google.archivepatcher.shared.TypedRange; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.List; 28 | 29 | /** 30 | * Tests for {@link PreDiffPlan}. 31 | */ 32 | @RunWith(JUnit4.class) 33 | @SuppressWarnings("javadoc") 34 | public class PreDiffPlanTest { 35 | private static final List> SORTED_VOID_LIST = 36 | Collections.unmodifiableList( 37 | Arrays.asList(new TypedRange(0, 1, null), new TypedRange(1, 1, null))); 38 | private static final List> SORTED_DEFLATE_LIST = 39 | Collections.unmodifiableList( 40 | Arrays.asList( 41 | new TypedRange(0, 1, JreDeflateParameters.of(1, 0, true)), 42 | new TypedRange(1, 1, JreDeflateParameters.of(1, 0, true)))); 43 | 44 | private List reverse(List list) { 45 | List reversed = new ArrayList(list); 46 | Collections.reverse(reversed); 47 | return reversed; 48 | } 49 | 50 | @Test 51 | public void testConstructor_OrderOK() { 52 | new PreDiffPlan( 53 | Collections.emptyList(), SORTED_VOID_LIST, SORTED_DEFLATE_LIST); 54 | } 55 | 56 | @Test(expected = IllegalArgumentException.class) 57 | public void testConstructor_OldFileUncompressionOrderNotOK() { 58 | new PreDiffPlan( 59 | Collections.emptyList(), 60 | reverse(SORTED_VOID_LIST), 61 | SORTED_DEFLATE_LIST, 62 | SORTED_DEFLATE_LIST); 63 | } 64 | 65 | @Test(expected = IllegalArgumentException.class) 66 | public void testConstructor_NewFileUncompressionOrderNotOK() { 67 | new PreDiffPlan( 68 | Collections.emptyList(), 69 | SORTED_VOID_LIST, 70 | reverse(SORTED_DEFLATE_LIST), 71 | SORTED_DEFLATE_LIST); 72 | } 73 | 74 | @Test(expected = IllegalArgumentException.class) 75 | public void testConstructor_NewFileRecompressionOrderNotOK() { 76 | new PreDiffPlan( 77 | Collections.emptyList(), 78 | SORTED_VOID_LIST, 79 | SORTED_DEFLATE_LIST, 80 | reverse(SORTED_DEFLATE_LIST)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/QualifiedRecommendationTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import java.util.Arrays; 18 | import java.util.Collections; 19 | import java.util.HashSet; 20 | import java.util.List; 21 | import java.util.Set; 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.junit.runners.JUnit4; 26 | 27 | /** Tests for {@link QualifiedRecommendation}. */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class QualifiedRecommendationTest { 31 | private static final byte[] FILENAME1 = {'f', 'o', 'o'}; 32 | private static final byte[] FILENAME2 = {'b', 'a', 'r'}; 33 | private static final MinimalZipEntry ENTRY1 = new MinimalZipEntry(0, 1, 2, 3, FILENAME1, true, 0); 34 | private static final MinimalZipEntry ENTRY2 = new MinimalZipEntry(1, 2, 3, 4, FILENAME2, true, 0); 35 | 36 | private static final QualifiedRecommendation DEFAULT_QUALIFIED_RECOMMENDATION = 37 | new QualifiedRecommendation( 38 | ENTRY1, 39 | ENTRY2, 40 | Recommendation.UNCOMPRESS_BOTH, 41 | RecommendationReason.COMPRESSED_BYTES_CHANGED); 42 | private static final QualifiedRecommendation CLONED_DEFAULT_QUALIFIED_RECOMMENDATION = 43 | new QualifiedRecommendation( 44 | ENTRY1, 45 | ENTRY2, 46 | Recommendation.UNCOMPRESS_BOTH, 47 | RecommendationReason.COMPRESSED_BYTES_CHANGED); 48 | private static final QualifiedRecommendation ALTERED_ENTRY1 = 49 | new QualifiedRecommendation( 50 | ENTRY2, 51 | ENTRY2, 52 | Recommendation.UNCOMPRESS_BOTH, 53 | RecommendationReason.COMPRESSED_BYTES_CHANGED); 54 | private static final QualifiedRecommendation ALTERED_ENTRY2 = 55 | new QualifiedRecommendation( 56 | ENTRY1, 57 | ENTRY1, 58 | Recommendation.UNCOMPRESS_BOTH, 59 | RecommendationReason.COMPRESSED_BYTES_CHANGED); 60 | private static final QualifiedRecommendation ALTERED_RECOMMENDATION = 61 | new QualifiedRecommendation( 62 | ENTRY1, 63 | ENTRY2, 64 | Recommendation.UNCOMPRESS_NEITHER, 65 | RecommendationReason.COMPRESSED_BYTES_CHANGED); 66 | private static final QualifiedRecommendation ALTERED_REASON = 67 | new QualifiedRecommendation( 68 | ENTRY1, ENTRY2, Recommendation.UNCOMPRESS_BOTH, RecommendationReason.UNSUITABLE); 69 | private static final List ALL_MUTATIONS = 70 | Collections.unmodifiableList( 71 | Arrays.asList(ALTERED_ENTRY1, ALTERED_ENTRY2, ALTERED_RECOMMENDATION, ALTERED_REASON)); 72 | 73 | @Test 74 | @SuppressWarnings("EqualsIncompatibleType") // For ErrorProne 75 | public void testEquals() { 76 | Assert.assertEquals(DEFAULT_QUALIFIED_RECOMMENDATION, DEFAULT_QUALIFIED_RECOMMENDATION); 77 | Assert.assertEquals(DEFAULT_QUALIFIED_RECOMMENDATION, CLONED_DEFAULT_QUALIFIED_RECOMMENDATION); 78 | Assert.assertNotSame(DEFAULT_QUALIFIED_RECOMMENDATION, CLONED_DEFAULT_QUALIFIED_RECOMMENDATION); 79 | for (QualifiedRecommendation mutation : ALL_MUTATIONS) { 80 | Assert.assertNotEquals(DEFAULT_QUALIFIED_RECOMMENDATION, mutation); 81 | } 82 | Assert.assertFalse(DEFAULT_QUALIFIED_RECOMMENDATION.equals(null)); 83 | Assert.assertFalse(DEFAULT_QUALIFIED_RECOMMENDATION.equals("foo")); 84 | } 85 | 86 | @Test 87 | public void testHashCode() { 88 | Set hashSet = new HashSet<>(); 89 | hashSet.add(DEFAULT_QUALIFIED_RECOMMENDATION); 90 | hashSet.add(CLONED_DEFAULT_QUALIFIED_RECOMMENDATION); 91 | Assert.assertEquals(1, hashSet.size()); 92 | hashSet.addAll(ALL_MUTATIONS); 93 | Assert.assertEquals(1 + ALL_MUTATIONS.size(), hashSet.size()); 94 | } 95 | 96 | @Test 97 | public void testGetters() { 98 | Assert.assertEquals(ENTRY1, DEFAULT_QUALIFIED_RECOMMENDATION.getOldEntry()); 99 | Assert.assertEquals(ENTRY2, DEFAULT_QUALIFIED_RECOMMENDATION.getNewEntry()); 100 | Assert.assertEquals( 101 | Recommendation.UNCOMPRESS_BOTH, DEFAULT_QUALIFIED_RECOMMENDATION.getRecommendation()); 102 | Assert.assertEquals( 103 | RecommendationReason.COMPRESSED_BYTES_CHANGED, 104 | DEFAULT_QUALIFIED_RECOMMENDATION.getReason()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/TempFileHolderTest.java: -------------------------------------------------------------------------------- 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.generator; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | 25 | /** 26 | * Tests for {@link TempFileHolder}. 27 | */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class TempFileHolderTest { 31 | @Test 32 | public void testConstructAndClose() throws IOException { 33 | // Tests that a temp file can be created and that it is deleted upon close(). 34 | File allocated = null; 35 | try(TempFileHolder holder = new TempFileHolder()) { 36 | Assert.assertNotNull(holder.file); 37 | Assert.assertTrue(holder.file.exists()); 38 | allocated = holder.file; 39 | } 40 | Assert.assertFalse(allocated.exists()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/bsdiff/BsUtilTest.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.IOException; 25 | import java.nio.charset.Charset; 26 | 27 | @RunWith(JUnit4.class) 28 | public class BsUtilTest { 29 | @Test 30 | public void writeFormattedLongTest() throws IOException { 31 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16); 32 | BsUtil.writeFormattedLong(0x12345678, outputStream); 33 | BsUtil.writeFormattedLong(0x0eadbeef, outputStream); 34 | byte[] actual = outputStream.toByteArray(); 35 | 36 | byte[] expected = { 37 | (byte) 0x78, 38 | (byte) 0x56, 39 | (byte) 0x34, 40 | (byte) 0x12, 41 | (byte) 0, 42 | (byte) 0, 43 | (byte) 0, 44 | (byte) 0, 45 | (byte) 0xef, 46 | (byte) 0xbe, 47 | (byte) 0xad, 48 | (byte) 0x0e, 49 | (byte) 0, 50 | (byte) 0, 51 | (byte) 0, 52 | (byte) 0 53 | }; 54 | Assert.assertArrayEquals(expected, actual); 55 | } 56 | 57 | @Test 58 | public void readFormattedLongTest() throws IOException { 59 | byte[] data = { 60 | (byte) 0x78, 61 | (byte) 0x56, 62 | (byte) 0x34, 63 | (byte) 0x12, 64 | (byte) 0, 65 | (byte) 0, 66 | (byte) 0, 67 | (byte) 0, 68 | (byte) 0xef, 69 | (byte) 0xbe, 70 | (byte) 0xad, 71 | (byte) 0x0e, 72 | (byte) 0, 73 | (byte) 0, 74 | (byte) 0, 75 | (byte) 0 76 | }; 77 | ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 78 | 79 | Assert.assertEquals(0x12345678, BsUtil.readFormattedLong(inputStream)); 80 | Assert.assertEquals(0x0eadbeef, BsUtil.readFormattedLong(inputStream)); 81 | } 82 | 83 | private long writeThenReadFormattedLong(long value) throws IOException { 84 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8); 85 | BsUtil.writeFormattedLong(value, outputStream); 86 | byte[] outputBytes = outputStream.toByteArray(); 87 | ByteArrayInputStream inputStream = new ByteArrayInputStream(outputBytes); 88 | return BsUtil.readFormattedLong(inputStream); 89 | } 90 | 91 | @Test 92 | public void writeThenReadFormattedLongTest() throws IOException { 93 | Assert.assertEquals(-1, writeThenReadFormattedLong(-1)); 94 | Assert.assertEquals(0x7fffffff, writeThenReadFormattedLong(0x7fffffff)); 95 | Assert.assertEquals(0, writeThenReadFormattedLong(0)); 96 | Assert.assertEquals(Long.MAX_VALUE, writeThenReadFormattedLong(Long.MAX_VALUE)); 97 | Assert.assertEquals(Long.MIN_VALUE, writeThenReadFormattedLong(Long.MIN_VALUE)); 98 | } 99 | 100 | @Test 101 | public void lexicographicalCompareTest() throws IOException { 102 | String s1 = "this is a string"; 103 | String s2 = "that was a string"; 104 | byte[] s1b = s1.getBytes(Charset.forName("US-ASCII")); 105 | byte[] s2b = s2.getBytes(Charset.forName("US-ASCII")); 106 | RandomAccessObject s1ro = new RandomAccessObject.RandomAccessByteArrayObject(s1b); 107 | RandomAccessObject s2ro = new RandomAccessObject.RandomAccessByteArrayObject(s2b); 108 | 109 | int r = BsUtil.lexicographicalCompare(s1ro, 0, s1b.length, s2ro, 0, s2b.length); 110 | Assert.assertTrue(r > 0); 111 | 112 | r = BsUtil.lexicographicalCompare(s1ro, 5, s1b.length - 5, s2ro, 5, s2b.length - 5); 113 | Assert.assertTrue(r < 0); 114 | 115 | r = BsUtil.lexicographicalCompare(s1ro, 7, s1b.length - 7, s2ro, 8, s2b.length - 7); 116 | Assert.assertTrue(r < 0); 117 | 118 | r = BsUtil.lexicographicalCompare(s1ro, 7, s1b.length - 8, s2ro, 8, s2b.length - 8); 119 | Assert.assertTrue(r < 0); 120 | 121 | r = BsUtil.lexicographicalCompare(s1ro, 0, 2, s2ro, 0, 2); 122 | Assert.assertEquals(0, r); 123 | 124 | r = BsUtil.lexicographicalCompare(s1ro, 0, 1, s2ro, 0, 2); 125 | Assert.assertTrue(r < 0); 126 | 127 | r = BsUtil.lexicographicalCompare(s1ro, 0, 2, s2ro, 0, 1); 128 | Assert.assertTrue(r > 0); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/bsdiff/DivSuffixSorterTest.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import org.junit.Before; 18 | import org.junit.runner.RunWith; 19 | import org.junit.runners.JUnit4; 20 | 21 | @RunWith(JUnit4.class) 22 | public class DivSuffixSorterTest extends SuffixSorterTestBase { 23 | 24 | DivSuffixSorter divSuffixSorter; 25 | 26 | @Before 27 | public void setup() { 28 | divSuffixSorter = 29 | new DivSuffixSorter(new RandomAccessObjectFactory.RandomAccessByteArrayObjectFactory()); 30 | } 31 | 32 | @Override 33 | public SuffixSorter getSuffixSorter() { 34 | return divSuffixSorter; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /generator/src/test/java/com/google/archivepatcher/generator/bsdiff/SuffixSorterTestBase.java: -------------------------------------------------------------------------------- 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.generator.bsdiff; 16 | 17 | import static org.junit.Assert.fail; 18 | 19 | import java.io.IOException; 20 | import java.util.Random; 21 | import org.junit.Assert; 22 | import org.junit.Test; 23 | 24 | /** 25 | * Base class for suffix sorter tests with common tests for a suffix sorter algorithm. 26 | */ 27 | public abstract class SuffixSorterTestBase { 28 | 29 | public abstract SuffixSorter getSuffixSorter(); 30 | 31 | @Test 32 | public void suffixSortEmptyDataTest() throws Exception { 33 | checkSuffixSort( new int[] {0}, new byte[] {}); 34 | } 35 | 36 | @Test 37 | public void suffixSortShortDataTest() throws Exception { 38 | checkSuffixSort(new int[] {1, 0}, new byte[] {23}); 39 | checkSuffixSort(new int[] {2, 1, 0}, new byte[] {23, 20}); 40 | checkSuffixSort(new int[] {2, 0, 1}, new byte[] {0, 127}); 41 | checkSuffixSort(new int[] {2, 1, 0}, new byte[] {42, 42}); 42 | } 43 | 44 | private void checkSuffixSort(int[] expectedSuffixArray, byte[] inputBytes) throws Exception { 45 | RandomAccessObject input = new RandomAccessObject.RandomAccessByteArrayObject(inputBytes); 46 | RandomAccessObject groupArray = getSuffixSorter().suffixSort(input); 47 | 48 | assertSorted(groupArray, input); 49 | Assert.assertArrayEquals(expectedSuffixArray, randomAccessObjectToIntArray(groupArray)); 50 | } 51 | 52 | @Test 53 | public void suffixSortLongDataTest() throws Exception { 54 | RandomAccessObject groupArrayRO = getSuffixSorter().suffixSort(BsDiffTestData.LONG_DATA_99_RO); 55 | 56 | assertSorted(groupArrayRO, BsDiffTestData.LONG_DATA_99_RO); 57 | 58 | Assert.assertArrayEquals( 59 | BsDiffTestData.QUICK_SUFFIX_SORT_TEST_GA_CONTROL, 60 | randomAccessObjectToIntArray(groupArrayRO)); 61 | } 62 | 63 | @Test 64 | public void suffixSortVeryLongDataTest() throws Exception { 65 | RandomAccessObject groupArray2RO = 66 | getSuffixSorter().suffixSort(BsDiffTestData.LONGER_DATA_349_RO); 67 | 68 | assertSorted(groupArray2RO, BsDiffTestData.LONGER_DATA_349_RO); 69 | 70 | Assert.assertArrayEquals( 71 | BsDiffTestData.QUICK_SUFFIX_SORT_TEST_IA_CONTROL, 72 | randomAccessObjectToIntArray(groupArray2RO)); 73 | } 74 | 75 | @Test 76 | public void testRandom() throws Exception { 77 | Random rand = new Random(1123458); 78 | for (int i = 1; i <= 10; i++) { 79 | RandomAccessObject input = generateRandom(rand, i * 10000); 80 | RandomAccessObject suffixArray = getSuffixSorter().suffixSort(input); 81 | 82 | assertSorted(suffixArray, input); 83 | } 84 | } 85 | 86 | private static RandomAccessObject generateRandom(Random rand, int length) { 87 | byte[] bytes = new byte[length]; 88 | rand.nextBytes(bytes); 89 | return new RandomAccessObject.RandomAccessByteArrayObject(bytes); 90 | } 91 | 92 | protected static RandomAccessObject intArrayToRandomAccessObject(final int[] array) 93 | throws Exception { 94 | RandomAccessObject ret = 95 | new RandomAccessObject.RandomAccessByteArrayObject(new byte[array.length * 4]); 96 | ret.seekToIntAligned(0); 97 | 98 | for (int element : array) { 99 | ret.writeInt(element); 100 | } 101 | 102 | return ret; 103 | } 104 | 105 | protected static boolean intArrayEqualsRandomAccessObject( 106 | int[] array, RandomAccessObject randomAccessObject) throws Exception { 107 | randomAccessObject.seekToIntAligned(0); 108 | 109 | for (int element : array) { 110 | if (element != randomAccessObject.readInt()) { 111 | return false; 112 | } 113 | } 114 | 115 | return true; 116 | } 117 | 118 | protected static int[] randomAccessObjectToIntArray(RandomAccessObject randomAccessObject) 119 | throws Exception { 120 | int[] ret = new int[(int) (randomAccessObject.length() / 4)]; 121 | randomAccessObject.seekToIntAligned(0); 122 | 123 | for (int i = 0; i < ret.length; i++) { 124 | ret[i] = randomAccessObject.readInt(); 125 | } 126 | 127 | return ret; 128 | } 129 | 130 | private static boolean checkSuffixLessThanOrEqual( 131 | RandomAccessObject input, int index1, int index2) throws Exception { 132 | while (true) { 133 | if (index1 == input.length()) { 134 | return true; 135 | } 136 | input.seek(index1); 137 | int unsignedByte1 = input.readUnsignedByte(); 138 | input.seek(index2); 139 | int unsignedByte2 = input.readUnsignedByte(); 140 | if (unsignedByte1 < unsignedByte2) { 141 | return true; 142 | } 143 | if (unsignedByte1 > unsignedByte2) { 144 | return false; 145 | } 146 | index1++; 147 | index2++; 148 | } 149 | } 150 | 151 | private static void assertSorted(RandomAccessObject suffixArray, RandomAccessObject input) 152 | throws Exception { 153 | for (int i = 0; i < input.length(); i++) { 154 | suffixArray.seekToIntAligned(i); 155 | int index1 = suffixArray.readInt(); 156 | suffixArray.seekToIntAligned(i+1); 157 | int index2 = suffixArray.readInt(); 158 | if (!checkSuffixLessThanOrEqual(input, index1, index2)) { 159 | fail(); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /generator/src/test/resources/com/google/archivepatcher/generator/bsdiff/testdata/BsDiffInternalTestNew.txt: -------------------------------------------------------------------------------- 1 | TL?I"a)tAARPRLH)ZNuYRsdf8yu032D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bKo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk}+A2lO;D#U<`bDa80CNDT)AvVZRq3' Gcn6*o3U_)`E={[q;,)T/5Ntk,>K=v3bo9pO}-?Ar/;\9epjz&`Y_i{FZw'@HTfLI\3(kOi^6{_9TC_m8C^zAuV'2hg%[AC@op(/=V+PvPNhk_(-O4P^_kOk} \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /integrationtest/build.gradle: -------------------------------------------------------------------------------- 1 | // intergationtest module 2 | apply plugin: 'java' 3 | 4 | dependencies { 5 | compile project(':shared') 6 | compile project(':applier') 7 | compile project(':generator') 8 | 9 | testCompile 'junit:junit:4.12' 10 | testCompile project(':sharedtest') 11 | } 12 | // EOF 13 | -------------------------------------------------------------------------------- /sample/src/main/java/com/google/archivepatcher/sample/SamplePatchApplier.java: -------------------------------------------------------------------------------- 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.sample; 16 | 17 | import com.google.archivepatcher.applier.FileByFileV1DeltaApplier; 18 | import com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.FileOutputStream; 22 | import java.util.zip.Inflater; 23 | import java.util.zip.InflaterInputStream; 24 | 25 | /** Apply a patch; args are old file path, patch file path, and new file path. */ 26 | public class SamplePatchApplier { 27 | public static void main(String... args) throws Exception { 28 | if (!new DefaultDeflateCompatibilityWindow().isCompatible()) { 29 | System.err.println("zlib not compatible on this system"); 30 | System.exit(-1); 31 | } 32 | File oldFile = new File(args[0]); // must be a zip archive 33 | Inflater uncompressor = new Inflater(true); 34 | try (FileInputStream compressedPatchIn = new FileInputStream(args[1]); 35 | InflaterInputStream patchIn = 36 | new InflaterInputStream(compressedPatchIn, uncompressor, 32768); 37 | FileOutputStream newFileOut = new FileOutputStream(args[2])) { 38 | new FileByFileV1DeltaApplier().applyDelta(oldFile, patchIn, newFileOut); 39 | } finally { 40 | uncompressor.end(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sample/src/main/java/com/google/archivepatcher/sample/SamplePatchGenerator.java: -------------------------------------------------------------------------------- 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.sample; 16 | 17 | import com.google.archivepatcher.generator.FileByFileV1DeltaGenerator; 18 | import com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow; 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.util.zip.Deflater; 22 | import java.util.zip.DeflaterOutputStream; 23 | 24 | /** Generate a patch; args are old file path, new file path, and patch file path. */ 25 | public class SamplePatchGenerator { 26 | public static void main(String... args) throws Exception { 27 | if (!new DefaultDeflateCompatibilityWindow().isCompatible()) { 28 | System.err.println("zlib not compatible on this system"); 29 | System.exit(-1); 30 | } 31 | File oldFile = new File(args[0]); // must be a zip archive 32 | File newFile = new File(args[1]); // must be a zip archive 33 | Deflater compressor = new Deflater(9, true); // to compress the patch 34 | try (FileOutputStream patchOut = new FileOutputStream(args[2]); 35 | DeflaterOutputStream compressedPatchOut = 36 | new DeflaterOutputStream(patchOut, compressor, 32768)) { 37 | new FileByFileV1DeltaGenerator().generateDelta(oldFile, newFile, compressedPatchOut); 38 | compressedPatchOut.finish(); 39 | compressedPatchOut.flush(); 40 | } finally { 41 | compressor.end(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':sharedtest' 2 | include ':shared' 3 | include ':applier' 4 | include ':generator' 5 | include ':explainer' 6 | include ':tools' 7 | include ':integrationtest' 8 | -------------------------------------------------------------------------------- /shared/build.gradle: -------------------------------------------------------------------------------- 1 | // shared module 2 | 3 | apply plugin: 'java' 4 | 5 | dependencies { 6 | testCompile 'junit:junit:4.12' 7 | testCompile project(':sharedtest') 8 | } 9 | // EOF 10 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/ByteArrayInputStreamFactory.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.IOException; 19 | 20 | /** 21 | * A {@link MultiViewInputStreamFactory} which creates {@link ByteArrayInputStream}s based on the 22 | * given {@code byte[]} in {@link #ByteArrayInputStreamFactory(byte[])}. 23 | */ 24 | public class ByteArrayInputStreamFactory implements MultiViewInputStreamFactory { 25 | 26 | private final byte[] bytes; 27 | 28 | public ByteArrayInputStreamFactory(byte[] bytes) { 29 | this.bytes = bytes; 30 | } 31 | 32 | @Override 33 | public ByteArrayInputStream newStream() throws IOException { 34 | return new ByteArrayInputStream(bytes); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/Compressor.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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.shared; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * An interface for implementing a streaming compressor. A compressor may be used to compress 23 | * arbitrary binary data, but is always capable of doing so in a streaming manner. 24 | */ 25 | public interface Compressor { 26 | /** 27 | * Compresses data, writing the compressed data into compressedOut. 28 | * 29 | * @param uncompressedIn the uncompressed data 30 | * @param compressedOut the compressed data 31 | * @throws IOException if something goes awry while reading or writing 32 | */ 33 | public void compress(InputStream uncompressedIn, OutputStream compressedOut) throws IOException; 34 | } 35 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/CountingOutputStream.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.FilterOutputStream; 18 | import java.io.IOException; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * Trivial output stream that counts the bytes written to it. 23 | */ 24 | public class CountingOutputStream extends FilterOutputStream { 25 | /** 26 | * Number of bytes written so far. 27 | */ 28 | private long bytesWritten = 0; 29 | 30 | /** 31 | * Create a new counting output stream. 32 | * @param out the output stream to wrap 33 | */ 34 | public CountingOutputStream(OutputStream out) { 35 | super(out); 36 | } 37 | 38 | /** 39 | * Returns the number of bytes written to this stream so far. 40 | * @return as described 41 | */ 42 | public long getNumBytesWritten() { 43 | return bytesWritten; 44 | } 45 | 46 | @Override 47 | public void write(int b) throws IOException { 48 | bytesWritten++; 49 | out.write(b); 50 | } 51 | 52 | @Override 53 | public void write(byte[] b) throws IOException { 54 | bytesWritten += b.length; 55 | out.write(b); 56 | } 57 | 58 | @Override 59 | public void write(byte[] b, int off, int len) throws IOException { 60 | bytesWritten += len; 61 | out.write(b, off, len); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/MultiViewInputStreamFactory.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | 20 | /** 21 | * A factory that produces multiple independent but identical byte streams exposed via the {@link 22 | * InputStream} class. 23 | */ 24 | public interface MultiViewInputStreamFactory { 25 | /** 26 | * Create and return a new {@link InputStream}. The returned stream is guaranteed to independently 27 | * produce the same byte sequence as any other stream obtained via a call to this method on the 28 | * same instance of this object. 29 | * 30 | * @return the stream 31 | * @throws IOException if something goes wrong 32 | */ 33 | public InputStream newStream() throws IOException; 34 | } 35 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/PartiallyUncompressingPipe.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.Closeable; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.OutputStream; 21 | 22 | /** 23 | * A pipe that moves data from an {@link InputStream} to an {@link OutputStream}, optionally 24 | * uncompressing the input data on-the-fly. 25 | */ 26 | public class PartiallyUncompressingPipe implements Closeable { 27 | /** 28 | * The uncompressor used to uncompress compressed input streams. 29 | */ 30 | private final DeflateUncompressor uncompressor; 31 | 32 | /** 33 | * The output stream to write to. 34 | */ 35 | private final CountingOutputStream out; 36 | 37 | /** 38 | * A buffer used when copying bytes. 39 | */ 40 | private final byte[] copyBuffer; 41 | 42 | /** 43 | * Modes available for {@link PartiallyUncompressingPipe#pipe(InputStream, Mode)}. 44 | */ 45 | public static enum Mode { 46 | /** 47 | * Copy bytes form the {@link InputStream} to the {@link OutputStream} without modification. 48 | */ 49 | COPY, 50 | 51 | /** 52 | * Treat the {@link InputStream} as a deflate stream with nowrap=false, uncompress the bytes 53 | * on-the-fly and write the uncompressed data to the {@link OutputStream}. 54 | */ 55 | UNCOMPRESS_WRAPPED, 56 | 57 | /** 58 | * Treat the {@link InputStream} as a deflate stream with nowrap=true, uncompress the bytes 59 | * on-the-fly and write the uncompressed data to the {@link OutputStream}. 60 | */ 61 | UNCOMPRESS_NOWRAP, 62 | } 63 | 64 | /** 65 | * Constructs a new stream. 66 | * @param out the stream, to write to 67 | * @param copyBufferSize the size of the buffer to use when copying instead of uncompressing 68 | */ 69 | public PartiallyUncompressingPipe(OutputStream out, int copyBufferSize) { 70 | this.out = new CountingOutputStream(out); 71 | uncompressor = new DeflateUncompressor(); 72 | uncompressor.setCaching(true); 73 | copyBuffer = new byte[copyBufferSize]; 74 | } 75 | 76 | /** 77 | * Pipes the entire contents of the specified {@link InputStream} to the configured 78 | * {@link OutputStream}, optionally uncompressing on-the-fly. 79 | * @param in the stream to read from 80 | * @param mode the mode to use for reading and writing 81 | * @return the number of bytes written to the output stream 82 | * @throws IOException if anything goes wrong 83 | */ 84 | public long pipe(InputStream in, Mode mode) throws IOException { 85 | long bytesWrittenBefore = out.getNumBytesWritten(); 86 | if (mode == Mode.COPY) { 87 | int numRead = 0; 88 | while ((numRead = in.read(copyBuffer)) >= 0) { 89 | out.write(copyBuffer, 0, numRead); 90 | } 91 | } else { 92 | uncompressor.setNowrap(mode == Mode.UNCOMPRESS_NOWRAP); 93 | uncompressor.uncompress(in, out); 94 | } 95 | out.flush(); 96 | return out.getNumBytesWritten() - bytesWrittenBefore; 97 | } 98 | 99 | /** 100 | * Returns the number of bytes written to the stream so far. 101 | * @return as described 102 | */ 103 | public long getNumBytesWritten() { 104 | return out.getNumBytesWritten(); 105 | } 106 | 107 | @Override 108 | public void close() throws IOException { 109 | uncompressor.release(); 110 | out.close(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/PatchConstants.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | /** 18 | * Constants used in reading and writing patches. 19 | */ 20 | public class PatchConstants { 21 | /** 22 | * The identifier that begins all patches of this type. 23 | */ 24 | public static final String IDENTIFIER = "GFbFv1_0"; // Google File-by-File v1.0 25 | 26 | /** 27 | * All available compatibility windows. The {@link #patchValue} field specifies the value for 28 | * each constant as represented in a patch file. 29 | */ 30 | public static enum CompatibilityWindowId { 31 | /** 32 | * The {@link com.google.archivepatcher.shared.DefaultDeflateCompatibilityWindow}. 33 | */ 34 | DEFAULT_DEFLATE((byte) 0); 35 | 36 | /** 37 | * The representation of this enumerated constant in patch files. 38 | */ 39 | public final byte patchValue; 40 | 41 | /** 42 | * Construct a new enumerated constant with the specified value in patch files. 43 | */ 44 | private CompatibilityWindowId(byte patchValue) { 45 | this.patchValue = patchValue; 46 | } 47 | 48 | /** 49 | * Parse a patch value and return the corresponding enumerated constant. 50 | * @param patchValue the patch value to parse 51 | * @return the corresponding enumerated constant, null if unmatched 52 | */ 53 | public static CompatibilityWindowId fromPatchValue(byte patchValue) { 54 | switch (patchValue) { 55 | case 0: 56 | return DEFAULT_DEFLATE; 57 | default: 58 | return null; 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * All available delta formats. The {@link #patchValue} field specifies the value for each 65 | * constant as represented in a patch file. 66 | */ 67 | public static enum DeltaFormat { 68 | /** 69 | * The bsdiff delta format. 70 | */ 71 | BSDIFF((byte) 0); 72 | 73 | /** 74 | * The representation of this enumerated constant in patch files. 75 | */ 76 | public final byte patchValue; 77 | 78 | /** 79 | * Construct a new enumerated constant with the specified value in patch files. 80 | */ 81 | private DeltaFormat(byte patchValue) { 82 | this.patchValue = patchValue; 83 | } 84 | 85 | /** 86 | * Parse a patch value and return the corresponding enumerated constant. 87 | * @param patchValue the patch value to parse 88 | * @return the corresponding enumerated constant, null if unmatched 89 | */ 90 | public static DeltaFormat fromPatchValue(byte patchValue) { 91 | switch (patchValue) { 92 | case 0: 93 | return BSDIFF; 94 | default: 95 | return null; 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/RandomAccessFileInputStreamFactory.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | 20 | /** 21 | * An implementation of {@link MultiViewInputStreamFactory} that produces instances of {@link 22 | * RandomAccessFileInputStream}. 23 | */ 24 | public class RandomAccessFileInputStreamFactory implements MultiViewInputStreamFactory { 25 | 26 | /** 27 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}. 28 | */ 29 | private final File file; 30 | 31 | /** 32 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}. 33 | */ 34 | private final long rangeOffset; 35 | 36 | /** 37 | * Argument for {@link RandomAccessFileInputStream#RandomAccessFileInputStream(File, long, long)}. 38 | */ 39 | private final long rangeLength; 40 | 41 | /** 42 | * Constructs a new factory that will create instances of {@link RandomAccessFileInputStream} with 43 | * the specified parameters. 44 | * @param file the file to use in {@link #newStream()} 45 | * @param rangeOffset the range offset to use in {@link #newStream()} 46 | * @param rangeLength the range length to use in {@link #newStream()} 47 | */ 48 | public RandomAccessFileInputStreamFactory(File file, long rangeOffset, long rangeLength) { 49 | this.file = file; 50 | this.rangeOffset = rangeOffset; 51 | this.rangeLength = rangeLength; 52 | } 53 | 54 | @Override 55 | public RandomAccessFileInputStream newStream() throws IOException { 56 | return new RandomAccessFileInputStream(file, rangeOffset, rangeLength); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/RandomAccessFileOutputStream.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.OutputStream; 20 | import java.io.RandomAccessFile; 21 | 22 | /** 23 | * An {@link OutputStream} backed by a file that will be written serially. Allows pre-allocating 24 | * the space for a stream as a file and then writing to that file as a stream. Call {@link #flush()} 25 | * to force the data to be written to the backing storage. 26 | */ 27 | public class RandomAccessFileOutputStream extends OutputStream { 28 | /** 29 | * The backing {@link RandomAccessFile}. 30 | */ 31 | private final RandomAccessFile raf; 32 | 33 | /** 34 | * Constructs a new instance that will immediately open the specified file for writing and set 35 | * the length to the specified value. 36 | * @param outputFile the file to wrap 37 | * @param expectedSize if greater than or equal to zero, the size to set the file to immediately; 38 | * otherwise, the file size is not set 39 | * @throws IOException if unable to open the file for writing or set the size 40 | */ 41 | public RandomAccessFileOutputStream(File outputFile, long expectedSize) throws IOException { 42 | this.raf = getRandomAccessFile(outputFile); 43 | if (expectedSize >= 0) { 44 | raf.setLength(expectedSize); 45 | if (raf.length() != expectedSize) { 46 | throw new IOException("Unable to set the file size"); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Given a {@link File}, get a writeable {@link RandomAccessFile} reference for it. 53 | * @param file the file 54 | * @return as described 55 | * @throws IOException if unable to open the file 56 | */ 57 | protected RandomAccessFile getRandomAccessFile(File file) throws IOException { 58 | return new RandomAccessFile(file, "rw"); 59 | } 60 | 61 | @Override 62 | public void write(int b) throws IOException { 63 | raf.write(b); 64 | } 65 | 66 | @Override 67 | public void write(byte[] b) throws IOException { 68 | write(b, 0, b.length); 69 | } 70 | 71 | @Override 72 | public void write(byte[] b, int off, int len) throws IOException { 73 | raf.write(b, off, len); 74 | } 75 | 76 | @Override 77 | public void flush() throws IOException { 78 | raf.getChannel().force(true); 79 | } 80 | 81 | @Override 82 | public void close() throws IOException { 83 | flush(); 84 | raf.close(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/TypedRange.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | /** 18 | * A range, annotated with metadata, that is represented as an offset and a length. Comparison is 19 | * performed based on the natural ordering of the offset field. 20 | * @param the type of the metadata 21 | */ 22 | public class TypedRange implements Comparable> { 23 | /** 24 | * The offset at which the range starts. 25 | */ 26 | private final long offset; 27 | 28 | /** 29 | * The length of the range. 30 | */ 31 | private final long length; 32 | 33 | /** 34 | * Optional metadata associated with this range. 35 | */ 36 | private final T metadata; 37 | 38 | /** 39 | * Constructs a new range with the specified parameters. 40 | * @param offset the offset at which the range starts 41 | * @param length the length of the range 42 | * @param metadata optional metadata associated with this range 43 | */ 44 | public TypedRange(long offset, long length, T metadata) { 45 | this.offset = offset; 46 | this.length = length; 47 | this.metadata = metadata; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "offset " + offset + ", length " + length + ", metadata " + metadata; 53 | } 54 | 55 | /** 56 | * Returns the offset at which the range starts. 57 | * @return as described 58 | */ 59 | public long getOffset() { 60 | return offset; 61 | } 62 | 63 | /** 64 | * Returns the length of the range. 65 | * @return as described 66 | */ 67 | public long getLength() { 68 | return length; 69 | } 70 | 71 | /** 72 | * Returns the metadata associated with the range, or null if no metadata has been set. 73 | * @return as described 74 | */ 75 | public T getMetadata() { 76 | return metadata; 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | final int prime = 31; 82 | int result = 1; 83 | result = prime * result + (int) (length ^ (length >>> 32)); 84 | result = prime * result + ((metadata == null) ? 0 : metadata.hashCode()); 85 | result = prime * result + (int) (offset ^ (offset >>> 32)); 86 | return result; 87 | } 88 | 89 | @Override 90 | public boolean equals(Object obj) { 91 | if (this == obj) return true; 92 | if (obj == null) return false; 93 | if (getClass() != obj.getClass()) return false; 94 | TypedRange other = (TypedRange) obj; 95 | if (length != other.length) return false; 96 | if (metadata == null) { 97 | if (other.metadata != null) return false; 98 | } else if (!metadata.equals(other.metadata)) return false; 99 | if (offset != other.offset) return false; 100 | return true; 101 | } 102 | 103 | @Override 104 | public int compareTo(TypedRange other) { 105 | if (getOffset() < other.getOffset()) { 106 | return -1; 107 | } else if (getOffset() > other.getOffset()) { 108 | return 1; 109 | } 110 | return 0; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /shared/src/main/java/com/google/archivepatcher/shared/Uncompressor.java: -------------------------------------------------------------------------------- 1 | // Copyright 2015 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.shared; 16 | 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.OutputStream; 20 | 21 | /** 22 | * An interface for implementing a streaming uncompressor. An uncompressor may be used to uncompress 23 | * data that was previously compressed by the corresponding {@link Compressor} implementation, and 24 | * always operates in a streaming manner. 25 | */ 26 | public interface Uncompressor { 27 | /** 28 | * Uncompresses data that was previously processed by the corresponding {@link Compressor} 29 | * implementation, writing the uncompressed data into uncompressedOut. 30 | * 31 | * @param compressedIn the compressed data 32 | * @param uncompressedOut the uncompressed data 33 | * @throws IOException if something goes awry while reading or writing 34 | */ 35 | public void uncompress(InputStream compressedIn, OutputStream uncompressedOut) throws IOException; 36 | } 37 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/CountingOutputStreamTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.junit.runners.JUnit4; 22 | 23 | import java.io.ByteArrayOutputStream; 24 | import java.io.IOException; 25 | import java.io.OutputStream; 26 | 27 | /** 28 | * Tests for {@link CountingOutputStream}. 29 | */ 30 | @RunWith(JUnit4.class) 31 | @SuppressWarnings("javadoc") 32 | public class CountingOutputStreamTest { 33 | private ByteArrayOutputStream outBuffer; 34 | private CountingOutputStream stream; 35 | 36 | /** 37 | * Helper class that discards all output. 38 | */ 39 | private static class NullStream extends OutputStream { 40 | @Override 41 | public void write(int b) throws IOException { 42 | // Do nothing 43 | } 44 | 45 | @Override 46 | public void write(byte[] b) throws IOException { 47 | // Do nothing 48 | } 49 | 50 | @Override 51 | public void write(byte[] b, int off, int len) throws IOException { 52 | // Do nothing 53 | } 54 | } 55 | 56 | @Before 57 | public void setup() { 58 | outBuffer = new ByteArrayOutputStream(); 59 | stream = new CountingOutputStream(outBuffer); 60 | } 61 | 62 | @Test 63 | public void testGetNumBytesWritten_Zero() { 64 | Assert.assertEquals(0, stream.getNumBytesWritten()); 65 | } 66 | 67 | @Test 68 | public void testGetNumBytesWritten_FewBytes() throws IOException { 69 | stream.write(1); 70 | Assert.assertEquals(1, stream.getNumBytesWritten()); 71 | stream.write(new byte[] {2, 3, 4}); 72 | Assert.assertEquals(4, stream.getNumBytesWritten()); 73 | stream.write(new byte[] {4, 5, 6, 7, 8}, 1, 3); // Write only {5, 6, 7} 74 | Assert.assertEquals(7, stream.getNumBytesWritten()); 75 | byte[] expected = new byte[] {1, 2, 3, 4, 5, 6, 7}; 76 | Assert.assertArrayEquals(expected, outBuffer.toByteArray()); 77 | } 78 | 79 | @Test 80 | public void testGetNumBytesWritten_PastIntegerMaxValue() throws IOException { 81 | // Make a 1MB buffer. Iterating over this 2048 times will take the test to the 2GB limit of 82 | // Integer.maxValue. Use a NullStream to avoid excessive memory usage and make the test fast. 83 | stream = new CountingOutputStream(new NullStream()); 84 | byte[] buffer = new byte[1024 * 1024]; 85 | for (int x = 0; x < 2048; x++) { 86 | stream.write(buffer); 87 | } 88 | long expected = 2048L * 1024L * 1024L; // == 2GB, Integer.MAX_VALUE + 1 89 | Assert.assertTrue(expected > Integer.MAX_VALUE); 90 | Assert.assertEquals(expected, stream.getNumBytesWritten()); 91 | // Push it well past 4GB 92 | for (int x = 0; x < 78053; x++) { 93 | stream.write(buffer); 94 | expected += buffer.length; 95 | Assert.assertEquals(expected, stream.getNumBytesWritten()); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/DefaultDeflateCompatibilityWindowTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import org.junit.Assert; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | /** 26 | * Tests for {@link DefaultDeflateCompatibilityWindow}. 27 | */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class DefaultDeflateCompatibilityWindowTest { 31 | 32 | private DefaultDeflateCompatibilityWindow window = null; 33 | 34 | private final JreDeflateParameters brokenParameters = JreDeflateParameters.of(1, 0, true); 35 | 36 | /** 37 | * Trivial subclass for testing, always fails compatibility checks. 38 | */ 39 | private class BrokenCompatibilityWindow extends DefaultDeflateCompatibilityWindow { 40 | @Override 41 | public Map getBaselineValues() { 42 | // Superclass's version is immutable, so make a copy. 43 | Map result = 44 | new HashMap(super.getBaselineValues()); 45 | result.put(brokenParameters, "foo"); 46 | return result; 47 | } 48 | } 49 | 50 | private class InfallibleCompatibilityWindow extends DefaultDeflateCompatibilityWindow { 51 | @Override 52 | public Map getBaselineValues() { 53 | // Using the system values for the baseline means the baseline will always match :) 54 | return getSystemValues(); 55 | } 56 | } 57 | 58 | @Before 59 | public void setUp() { 60 | window = new DefaultDeflateCompatibilityWindow(); 61 | } 62 | 63 | @Test 64 | public void testGetBaselineValues() { 65 | // Basic sanity test: ensure it doesn't crash and isn't null, and contains all the values that 66 | // we care about. 67 | ensureHasAllKeys(window.getBaselineValues()); 68 | } 69 | 70 | @Test 71 | public void testGetSystemValues() { 72 | // Basic sanity test: ensure it doesn't crash and isn't null, and contains all the values that 73 | // we care about. 74 | ensureHasAllKeys(window.getSystemValues()); 75 | } 76 | 77 | private void ensureHasAllKeys(Map mappings) { 78 | for (int level = 1; level <= 9; level++) { 79 | for (int strategy = 0; strategy <= 1; strategy++) { 80 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(level, strategy, true))); 81 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(level, strategy, false))); 82 | } 83 | } 84 | // Manually scan for presence of the strategy-2 values, only set for compression level 1.... 85 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(1, 2, true))); 86 | Assert.assertTrue(mappings.containsKey(JreDeflateParameters.of(1, 2, false))); 87 | Assert.assertEquals(38, mappings.size()); 88 | } 89 | 90 | @Test 91 | public void testGetCorpus() { 92 | // Basic sanity test: ensure it's a non-null, non-empty return. 93 | byte[] corpus1 = window.getCorpus(); 94 | Assert.assertNotNull(corpus1); 95 | Assert.assertTrue(corpus1.length > 0); 96 | // Basic sanity test: ensure the corpus is distinct each time the method is called (i.e., the 97 | // mutable object returned is independent of the actual corpus). 98 | byte[] corpus2 = window.getCorpus(); 99 | Assert.assertArrayEquals(corpus1, corpus2); 100 | Assert.assertNotSame(corpus1, corpus2); 101 | } 102 | 103 | @Test 104 | public void testIsCompatible() { 105 | // First do a coverage-only call, as it's not safe to assume compatibility in the unit test. 106 | window.isCompatible(); 107 | // Now do a call that is guaranteed to fail. 108 | BrokenCompatibilityWindow broken = new BrokenCompatibilityWindow(); 109 | Assert.assertFalse(broken.isCompatible()); 110 | // Now do a call that is guaranteed to succeed. 111 | InfallibleCompatibilityWindow infallible = new InfallibleCompatibilityWindow(); 112 | Assert.assertTrue(infallible.isCompatible()); 113 | } 114 | 115 | @Test 116 | public void testGetIncompatibleValues() { 117 | // First do a coverage-only call, as it's not safe to assume compatibility in the unit test. 118 | Assert.assertNotNull(window.getIncompatibleValues()); 119 | // Now do a call that is guaranteed to produce failure data. 120 | BrokenCompatibilityWindow brokenWindow = new BrokenCompatibilityWindow(); 121 | Map incompatible = brokenWindow.getIncompatibleValues(); 122 | Assert.assertTrue(incompatible.containsKey(brokenParameters)); 123 | // Now do a call that is guaranteed to produce no failure data. 124 | InfallibleCompatibilityWindow infallible = new InfallibleCompatibilityWindow(); 125 | Assert.assertTrue(infallible.getIncompatibleValues().isEmpty()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/JreDeflateParametersTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.JUnit4; 21 | 22 | /** 23 | * Tests for {@link DefaultDeflateCompatibilityWindow}. 24 | */ 25 | @RunWith(JUnit4.class) 26 | @SuppressWarnings("javadoc") 27 | public class JreDeflateParametersTest { 28 | 29 | @Test 30 | public void testOf_AllValidValues() { 31 | for (int level = 1; level <= 9; level++) { 32 | for (int strategy = 0; strategy <= 2; strategy++) { 33 | for (boolean nowrap : new boolean[] {true, false}) { 34 | JreDeflateParameters.of(level, strategy, nowrap); 35 | } 36 | } 37 | } 38 | } 39 | 40 | private void assertIllegalArgumentException(int level, int strategy, boolean nowrap) { 41 | try { 42 | JreDeflateParameters.of(level, strategy, nowrap); 43 | Assert.fail("Invalid configuration allowed"); 44 | } catch (IllegalArgumentException expected) { 45 | // Pass 46 | } 47 | } 48 | 49 | @Test 50 | public void testOf_InvalidValues() { 51 | // All of these should fail. 52 | assertIllegalArgumentException(0, 0, true); // Bad compression level (store) 53 | assertIllegalArgumentException(-1, 0, true); // Bad compression level (insane value < 0) 54 | assertIllegalArgumentException(10, 0, true); // Bad compression level (insane value > 9) 55 | assertIllegalArgumentException(1, -1, true); // Bad strategy (insane value < 0) 56 | assertIllegalArgumentException(1, 3, true); // Bad strategy (valid in zlib, unsupported in JRE) 57 | } 58 | 59 | @Test 60 | public void testToString() { 61 | // Ensure that toString() doesn't crash and produces a non-empty string. 62 | Assert.assertTrue(JreDeflateParameters.of(1, 0, true).toString().length() > 0); 63 | } 64 | 65 | @Test 66 | public void testParseString() { 67 | for (int level = 1; level <= 9; level++) { 68 | for (int strategy = 0; strategy <= 2; strategy++) { 69 | for (boolean nowrap : new boolean[] {true, false}) { 70 | JreDeflateParameters params = JreDeflateParameters.of(level, strategy, nowrap); 71 | String asString = params.toString(); 72 | JreDeflateParameters fromString = JreDeflateParameters.parseString(asString); 73 | Assert.assertEquals(params, fromString); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/PartiallyUncompressingPipeTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import com.google.archivepatcher.shared.PartiallyUncompressingPipe.Mode; 18 | 19 | import org.junit.Assert; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | import java.io.ByteArrayInputStream; 26 | import java.io.ByteArrayOutputStream; 27 | import java.io.IOException; 28 | 29 | /** 30 | * Tests for {@link PartiallyUncompressingPipe}. 31 | */ 32 | @RunWith(JUnit4.class) 33 | @SuppressWarnings("javadoc") 34 | public class PartiallyUncompressingPipeTest { 35 | private ByteArrayOutputStream outBuffer; 36 | private PartiallyUncompressingPipe stream; 37 | 38 | @Before 39 | public void setup() { 40 | outBuffer = new ByteArrayOutputStream(); 41 | stream = new PartiallyUncompressingPipe(outBuffer, 32768); 42 | } 43 | 44 | @Test 45 | public void testWriteAll_Uncompressed() throws IOException { 46 | byte[] expectedBytes = new byte[] {1, 2, 3, 4, 5}; 47 | stream.pipe(new ByteArrayInputStream(expectedBytes), Mode.COPY); 48 | Assert.assertArrayEquals(expectedBytes, outBuffer.toByteArray()); 49 | } 50 | 51 | @Test 52 | public void testWriteAll_Compressed_NoWrapTrue() throws IOException { 53 | UnitTestZipEntry entry = UnitTestZipArchive.makeUnitTestZipEntry("/foo", 7, "frobozz", null); 54 | stream.pipe( 55 | new ByteArrayInputStream(entry.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP); 56 | Assert.assertArrayEquals(entry.getUncompressedBinaryContent(), outBuffer.toByteArray()); 57 | } 58 | 59 | @Test 60 | public void testWriteAll_Compressed_NoWrapFalse() throws IOException { 61 | UnitTestZipEntry entry = UnitTestZipArchive.makeUnitTestZipEntry("/foo", 6, "frobozz", null); 62 | 63 | // Make a compressor with nowrap set to *false* (unusual) and pump the uncompressed entry 64 | // content through it. 65 | DeflateCompressor compressor = new DeflateCompressor(); 66 | compressor.setNowrap(false); 67 | ByteArrayOutputStream compressBuffer = new ByteArrayOutputStream(); 68 | compressor.compress( 69 | new ByteArrayInputStream(entry.getUncompressedBinaryContent()), compressBuffer); 70 | 71 | // Now use the compressed data as input to the PartiallyUncompressingPipe. 72 | stream.pipe(new ByteArrayInputStream(compressBuffer.toByteArray()), Mode.UNCOMPRESS_WRAPPED); 73 | Assert.assertArrayEquals(entry.getUncompressedBinaryContent(), outBuffer.toByteArray()); 74 | } 75 | 76 | @Test 77 | public void testWriteAll_Multiple() throws IOException { 78 | // A series of uncompressed, compressed, uncompressed, compressed, uncompressed bytes. 79 | UnitTestZipEntry entryA = 80 | UnitTestZipArchive.makeUnitTestZipEntry("/bar", 3, "dragon lance", null); 81 | UnitTestZipEntry entryB = 82 | UnitTestZipArchive.makeUnitTestZipEntry("/baz", 8, "kender & hoopak", null); 83 | ByteArrayOutputStream expected = new ByteArrayOutputStream(); 84 | 85 | // Write everything 86 | byte[] expectedBytes1 = new byte[] {1, 2, 3, 4, 5}; 87 | expected.write(expectedBytes1); 88 | stream.pipe(new ByteArrayInputStream(expectedBytes1), Mode.COPY); 89 | 90 | stream.pipe( 91 | new ByteArrayInputStream(entryA.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP); 92 | expected.write(entryA.getUncompressedBinaryContent()); 93 | 94 | byte[] expectedBytes3 = new byte[] {6, 7, 8, 9, 0}; 95 | stream.pipe(new ByteArrayInputStream(expectedBytes3), Mode.COPY); 96 | expected.write(expectedBytes3); 97 | 98 | stream.pipe( 99 | new ByteArrayInputStream(entryB.getCompressedBinaryContent()), Mode.UNCOMPRESS_NOWRAP); 100 | expected.write(entryB.getUncompressedBinaryContent()); 101 | 102 | byte[] expectedBytes5 = new byte[] {127, 127, 127, 127, 127, 127}; 103 | stream.pipe(new ByteArrayInputStream(expectedBytes5), Mode.COPY); 104 | expected.write(expectedBytes5); 105 | 106 | Assert.assertArrayEquals(expected.toByteArray(), outBuffer.toByteArray()); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/RandomAccessFileInputStreamFactoryTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import org.junit.After; 18 | import org.junit.Assert; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | import java.io.File; 25 | import java.io.FileOutputStream; 26 | import java.io.IOException; 27 | 28 | /** 29 | * Tests for {@link RandomAccessFileInputStreamFactory}. 30 | */ 31 | @RunWith(JUnit4.class) 32 | @SuppressWarnings("javadoc") 33 | public class RandomAccessFileInputStreamFactoryTest { 34 | /** 35 | * The object under test. 36 | */ 37 | private RandomAccessFileInputStreamFactory factory = null; 38 | 39 | /** 40 | * Test data written to the file. 41 | */ 42 | private byte[] testData = null; 43 | 44 | /** 45 | * The temp file. 46 | */ 47 | private File tempFile = null; 48 | 49 | @Before 50 | public void setup() throws IOException { 51 | testData = new byte[128]; 52 | for (int x = 0; x < 128; x++) { 53 | testData[x] = (byte) x; 54 | } 55 | tempFile = File.createTempFile("ra-fist", "tmp"); 56 | FileOutputStream out = new FileOutputStream(tempFile); 57 | out.write(testData); 58 | out.flush(); 59 | out.close(); 60 | tempFile.deleteOnExit(); 61 | factory = new RandomAccessFileInputStreamFactory(tempFile, 0, testData.length); 62 | } 63 | 64 | @After 65 | public void tearDown() { 66 | try { 67 | tempFile.delete(); 68 | } catch (Exception ignored) { 69 | // Nothing to do 70 | } 71 | } 72 | 73 | @Test 74 | public void testNewStream_MakesIdenticalStreams() throws IOException { 75 | RandomAccessFileInputStream rafis1 = factory.newStream(); 76 | RandomAccessFileInputStream rafis2 = factory.newStream(); 77 | try { 78 | Assert.assertNotSame(rafis1, rafis2); 79 | for (int x = 0; x < testData.length; x++) { 80 | Assert.assertEquals(x, rafis1.read()); 81 | Assert.assertEquals(x, rafis2.read()); 82 | } 83 | Assert.assertEquals(-1, rafis1.read()); 84 | Assert.assertEquals(-1, rafis2.read()); 85 | } finally { 86 | try { 87 | rafis1.close(); 88 | } catch (Exception ignored) { 89 | // Nothing 90 | } 91 | try { 92 | rafis2.close(); 93 | } catch (Exception ignored) { 94 | // Nothing 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/RandomAccessFileOutputStreamTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import org.junit.After; 18 | import org.junit.Assert; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.JUnit4; 23 | 24 | import java.io.DataInputStream; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.IOException; 28 | import java.io.RandomAccessFile; 29 | 30 | /** 31 | * Tests for {@link RandomAccessFileOutputStream}. 32 | */ 33 | @RunWith(JUnit4.class) 34 | @SuppressWarnings("javadoc") 35 | public class RandomAccessFileOutputStreamTest { 36 | /** 37 | * The object under test. 38 | */ 39 | private RandomAccessFileOutputStream stream = null; 40 | 41 | /** 42 | * Test data written to the file. 43 | */ 44 | private byte[] testData = null; 45 | 46 | /** 47 | * The temp file. 48 | */ 49 | private File tempFile = null; 50 | 51 | @Before 52 | public void setup() throws IOException { 53 | testData = new byte[128]; 54 | for (int x = 0; x < 128; x++) { 55 | testData[x] = (byte) x; 56 | } 57 | tempFile = File.createTempFile("ra-fost", "tmp"); 58 | tempFile.deleteOnExit(); 59 | } 60 | 61 | @After 62 | public void tearDown() { 63 | try { 64 | stream.close(); 65 | } catch (Exception ignored) { 66 | // Nothing to do 67 | } 68 | try { 69 | tempFile.delete(); 70 | } catch (Exception ignored) { 71 | // Nothing to do 72 | } 73 | } 74 | 75 | @Test 76 | public void testCreateAndSize() throws IOException { 77 | stream = new RandomAccessFileOutputStream(tempFile, 11L); 78 | Assert.assertEquals(11, tempFile.length()); 79 | } 80 | 81 | @Test(expected = IOException.class) 82 | public void testCreateAndFailToSize() throws IOException { 83 | stream = 84 | new RandomAccessFileOutputStream(tempFile, 11L) { 85 | @Override 86 | protected RandomAccessFile getRandomAccessFile(File file) throws IOException { 87 | return new RandomAccessFile(file, "rw") { 88 | @Override 89 | public void setLength(long newLength) throws IOException { 90 | // Do nothing, to trigger failure case in the constructor. 91 | } 92 | }; 93 | } 94 | }; 95 | } 96 | 97 | @Test 98 | public void testWrite() throws IOException { 99 | stream = new RandomAccessFileOutputStream(tempFile, 1L); 100 | stream.write(7); 101 | stream.flush(); 102 | stream.close(); 103 | FileInputStream in = null; 104 | try { 105 | in = new FileInputStream(tempFile); 106 | Assert.assertEquals(7, in.read()); 107 | } finally { 108 | try { 109 | in.close(); 110 | } catch (Exception ignored) { 111 | // Nothing 112 | } 113 | } 114 | } 115 | 116 | @Test 117 | public void testWriteArray() throws IOException { 118 | stream = new RandomAccessFileOutputStream(tempFile, 1L); 119 | stream.write(testData, 0, testData.length); 120 | stream.flush(); 121 | stream.close(); 122 | FileInputStream in = null; 123 | DataInputStream dataIn = null; 124 | try { 125 | in = new FileInputStream(tempFile); 126 | dataIn = new DataInputStream(in); 127 | byte[] actual = new byte[testData.length]; 128 | dataIn.readFully(actual); 129 | Assert.assertArrayEquals(testData, actual); 130 | } finally { 131 | if (dataIn != null) { 132 | try { 133 | dataIn.close(); 134 | } catch (Exception ignored) { 135 | // Nothing 136 | } 137 | } 138 | if (in != null) { 139 | try { 140 | in.close(); 141 | } catch (Exception ignored) { 142 | // Nothing 143 | } 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /shared/src/test/java/com/google/archivepatcher/shared/TypedRangeTest.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | import org.junit.Assert; 21 | import org.junit.Test; 22 | import org.junit.runner.RunWith; 23 | import org.junit.runners.JUnit4; 24 | 25 | /** 26 | * Tests for {@link TypedRange}. 27 | */ 28 | @RunWith(JUnit4.class) 29 | @SuppressWarnings("javadoc") 30 | public class TypedRangeTest { 31 | 32 | @Test 33 | public void testGetters() { 34 | String text = "hello"; 35 | TypedRange range = new TypedRange(555, 777, text); 36 | Assert.assertEquals(555, range.getOffset()); 37 | Assert.assertEquals(777, range.getLength()); 38 | Assert.assertSame(text, range.getMetadata()); 39 | } 40 | 41 | @Test 42 | public void testToString() { 43 | // Just make sure this doesn't crash. 44 | TypedRange range = new TypedRange(555, 777, "woohoo"); 45 | Assert.assertNotNull(range.toString()); 46 | Assert.assertFalse(range.toString().length() == 0); 47 | } 48 | 49 | @Test 50 | public void testCompare() { 51 | TypedRange range1 = new TypedRange(1, 777, null); 52 | TypedRange range2 = new TypedRange(2, 777, null); 53 | Assert.assertTrue(range1.compareTo(range2) < 0); 54 | Assert.assertTrue(range2.compareTo(range1) > 0); 55 | Assert.assertTrue(range1.compareTo(range1) == 0); 56 | } 57 | 58 | @Test 59 | public void testHashCode() { 60 | TypedRange range1a = new TypedRange(123, 456, "hi mom"); 61 | TypedRange range1b = new TypedRange(123, 456, "hi mom"); 62 | Assert.assertEquals(range1a.hashCode(), range1b.hashCode()); 63 | Set hashCodes = new HashSet(); 64 | hashCodes.add(range1a.hashCode()); 65 | hashCodes.add(new TypedRange(123 + 1, 456, "hi mom").hashCode()); // offset changed 66 | hashCodes.add(new TypedRange(123, 456 + 1, "hi mom").hashCode()); // length changed 67 | hashCodes.add(new TypedRange(123 + 1, 456, "x").hashCode()); // metadata changed 68 | hashCodes.add(new TypedRange(123 + 1, 456, null).hashCode()); // no metadata at all 69 | // Assert that all 4 hash codes are unique 70 | Assert.assertEquals(5, hashCodes.size()); 71 | } 72 | 73 | @Test 74 | public void testEquals() { 75 | TypedRange range1a = new TypedRange(123, 456, "hi mom"); 76 | Assert.assertEquals(range1a, range1a); // identity case 77 | TypedRange range1b = new TypedRange(123, 456, "hi mom"); 78 | Assert.assertEquals(range1a, range1b); // equality case 79 | Assert.assertNotEquals(range1a, new TypedRange(123 + 1, 456, "hi mom")); // offset 80 | Assert.assertNotEquals(range1a, new TypedRange(123, 456 + 1, "hi mom")); // length 81 | Assert.assertNotEquals(range1a, new TypedRange(123, 456, "foo")); // metadata 82 | Assert.assertNotEquals(range1a, new TypedRange(123, 456, null)); // no metadata 83 | Assert.assertNotEquals(new TypedRange(123, 456, null), range1a); // other code branch 84 | Assert.assertEquals( 85 | new TypedRange(123, 456, null), 86 | new TypedRange(123, 456, null)); // both with null metadata 87 | Assert.assertNotEquals(range1a, null); // versus null 88 | Assert.assertNotEquals(range1a, "space channel 5"); // versus object of different class 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /sharedtest/build.gradle: -------------------------------------------------------------------------------- 1 | // sharedtest module 2 | 3 | apply plugin: 'java' 4 | 5 | dependencies { 6 | compile 'junit:junit:4.12' 7 | compile project(':shared') 8 | } 9 | // EOF 10 | -------------------------------------------------------------------------------- /sharedtest/src/main/java/com/google/archivepatcher/shared/UnitTestZipEntry.java: -------------------------------------------------------------------------------- 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.shared; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.IOException; 20 | import java.io.UnsupportedEncodingException; 21 | 22 | /** 23 | * Data for one entry in the zip returned by {@link UnitTestZipArchive#makeTestZip()}. 24 | */ 25 | public class UnitTestZipEntry { 26 | /** 27 | * The path under which the data is located in the archive. 28 | */ 29 | public final String path; 30 | 31 | /** 32 | * The compression level of the entry. 33 | */ 34 | public final int level; 35 | 36 | /** 37 | * The binary content of the entry. 38 | */ 39 | public final String content; 40 | 41 | /** 42 | * Optional comment, as an ASCII string. 43 | */ 44 | public final String comment; 45 | 46 | /** 47 | * Whether or not to use nowrap. 48 | */ 49 | public final boolean nowrap; 50 | 51 | /** 52 | * Creates a new entry with nowrap=true. 53 | * @param path the path under which the data is located in the archive 54 | * @param level the compression level of the entry 55 | * @param content the binary content of the entry, as an ASCII string 56 | * @param comment optional comment, as an ASCII string 57 | */ 58 | public UnitTestZipEntry(String path, int level, String content, String comment) { 59 | this(path, level, true, content, comment); 60 | } 61 | 62 | /** 63 | * Creates a new entry. 64 | * 65 | * @param path the path under which the data is located in the archive 66 | * @param level the compression level of the entry 67 | * @param nowrap the wrapping mode (false to wrap the entry like gzip, true otherwise) 68 | * @param content the binary content of the entry, as an ASCII string 69 | * @param comment optional comment, as an ASCII string 70 | */ 71 | public UnitTestZipEntry(String path, int level, boolean nowrap, String content, String comment) { 72 | this.path = path; 73 | this.level = level; 74 | this.nowrap = nowrap; 75 | this.content = content; 76 | this.comment = comment; 77 | } 78 | 79 | /** 80 | * Returns the uncompressed content of the entry as a byte array for unit test simplicity. If the 81 | * level is 0, this is the same as the actual array of bytes that will be present in the zip 82 | * archive. If the level is not 0, this is the result of uncompressed the bytes that are present 83 | * in the zip archive for this entry. 84 | * @return as described 85 | */ 86 | public byte[] getUncompressedBinaryContent() { 87 | try { 88 | return content.getBytes("US-ASCII"); 89 | } catch (UnsupportedEncodingException e) { 90 | throw new RuntimeException("System doesn't support US-ASCII"); // Not likely 91 | } 92 | } 93 | 94 | /** 95 | * Returns the compressed form of the content, according to the level, that should be found in the 96 | * zip archive. If the level is 0 (store, i.e. not compressed) this is the same as calling 97 | * {@link #getUncompressedBinaryContent()}. 98 | * @return the content, as a byte array 99 | */ 100 | public byte[] getCompressedBinaryContent() { 101 | if (level == 0) { 102 | return getUncompressedBinaryContent(); 103 | } 104 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 105 | DeflateCompressor compressor = new DeflateCompressor(); 106 | compressor.setCompressionLevel(level); 107 | compressor.setNowrap(nowrap); 108 | try { 109 | compressor.compress(new ByteArrayInputStream(getUncompressedBinaryContent()), buffer); 110 | } catch (IOException e) { 111 | throw new RuntimeException(e); // Shouldn't happen as this is all in-memory 112 | } 113 | return buffer.toByteArray(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tools/build.gradle: -------------------------------------------------------------------------------- 1 | // tools module 2 | 3 | apply plugin: 'java' 4 | 5 | def mainClassName = 'com.google.archivepatcher.tools.FileByFileTool' 6 | jar { 7 | manifest { 8 | attributes "Main-Class": mainClassName 9 | } 10 | 11 | from { 12 | configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } 13 | } 14 | } 15 | 16 | dependencies { 17 | compile project(':applier') 18 | compile project(':explainer') 19 | compile project(':generator') 20 | compile project(':shared') 21 | } 22 | // EOF 23 | -------------------------------------------------------------------------------- /tools/src/main/java/com/google/archivepatcher/tools/AbstractTool.java: -------------------------------------------------------------------------------- 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.tools; 16 | 17 | import java.io.File; 18 | import java.util.Iterator; 19 | 20 | /** 21 | * Simple base class for tools. Minimal standalone functionality free of third-party argument parser 22 | * dependencies. 23 | */ 24 | public abstract class AbstractTool { 25 | 26 | /** 27 | * Pop an argument from the argument iterator or exit with a usage message about the expected 28 | * type of argument that was supposed to be found. 29 | * @param iterator the iterator to take an element from if available 30 | * @param expectedType description for the thing that was supposed to be in the iterator, for 31 | * error messages 32 | * @return the element retrieved from the iterator 33 | */ 34 | protected String popOrDie(Iterator iterator, String expectedType) { 35 | if (!iterator.hasNext()) { 36 | exitWithUsage("missing argument for " + expectedType); 37 | } 38 | return iterator.next(); 39 | } 40 | 41 | /** 42 | * Find and return a readable file if it exists, exit with a usage message if it does not. 43 | * @param path the path to check and get a {@link File} for 44 | * @param description what the file represents, for error messages 45 | * @return a {@link File} representing the path, which exists and is readable 46 | */ 47 | protected File getRequiredFileOrDie(String path, String description) { 48 | File result = new File(path); 49 | if (!result.exists() || !result.canRead()) { 50 | exitWithUsage(description + " does not exist or cannot be read: " + path); 51 | } 52 | return result; 53 | } 54 | 55 | /** 56 | * Terminate the program with an error message and usage instructions. 57 | * @param message the error message to give to the user prior to the usage instructions 58 | */ 59 | protected void exitWithUsage(String message) { 60 | System.err.println("Error: " + message); 61 | System.err.println(getUsage()); 62 | System.exit(1); 63 | } 64 | 65 | /** 66 | * Returns a string describing the usage for this tool. 67 | * @return the string 68 | */ 69 | protected abstract String getUsage(); 70 | } 71 | --------------------------------------------------------------------------------