1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.fastdeploy; 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 import java.util.Arrays; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.Comparator; 28 29 import com.android.tools.build.apkzlib.zip.ZFile; 30 import com.android.tools.build.apkzlib.zip.ZFileOptions; 31 import com.android.tools.build.apkzlib.zip.StoredEntry; 32 import com.android.tools.build.apkzlib.zip.StoredEntryType; 33 import com.android.tools.build.apkzlib.zip.CentralDirectoryHeaderCompressInfo; 34 import com.android.tools.build.apkzlib.zip.CentralDirectoryHeader; 35 36 import com.android.fastdeploy.APKMetaData; 37 import com.android.fastdeploy.APKEntry; 38 39 class PatchUtils { 40 private static final long NEGATIVE_MASK = 1L << 63; 41 private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63; 42 public static final String SIGNATURE = "HAMADI/IHD"; 43 getOffsetFromEntry(StoredEntry entry)44 private static long getOffsetFromEntry(StoredEntry entry) { 45 return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize(); 46 } 47 getAPKMetaData(File apkFile)48 public static APKMetaData getAPKMetaData(File apkFile) throws IOException { 49 APKMetaData.Builder apkEntriesBuilder = APKMetaData.newBuilder(); 50 ZFileOptions options = new ZFileOptions(); 51 ZFile zFile = new ZFile(apkFile, options); 52 53 ArrayList<StoredEntry> metaDataEntries = new ArrayList<StoredEntry>(); 54 55 for (StoredEntry entry : zFile.entries()) { 56 if (entry.getType() != StoredEntryType.FILE) { 57 continue; 58 } 59 metaDataEntries.add(entry); 60 } 61 62 Collections.sort(metaDataEntries, new Comparator<StoredEntry>() { 63 private long getOffsetFromEntry(StoredEntry entry) { 64 return PatchUtils.getOffsetFromEntry(entry); 65 } 66 67 @Override 68 public int compare(StoredEntry lhs, StoredEntry rhs) { 69 // -1 - less than, 1 - greater than, 0 - equal, all inversed for descending 70 return Long.compare(getOffsetFromEntry(lhs), getOffsetFromEntry(rhs)); 71 } 72 }); 73 74 for (StoredEntry entry : metaDataEntries) { 75 CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader(); 76 CentralDirectoryHeaderCompressInfo cdhci = cdh.getCompressionInfoWithWait(); 77 78 APKEntry.Builder entryBuilder = APKEntry.newBuilder(); 79 entryBuilder.setCrc32(cdh.getCrc32()); 80 entryBuilder.setFileName(cdh.getName()); 81 entryBuilder.setCompressedSize(cdhci.getCompressedSize()); 82 entryBuilder.setUncompressedSize(cdh.getUncompressedSize()); 83 entryBuilder.setDataOffset(getOffsetFromEntry(entry)); 84 85 apkEntriesBuilder.addEntries(entryBuilder); 86 apkEntriesBuilder.build(); 87 } 88 return apkEntriesBuilder.build(); 89 } 90 91 /** 92 * Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant 93 * byte is written first and the most significant byte is written last. 94 * @param value the value to write 95 * @param outputStream the stream to write to 96 */ writeFormattedLong(final long value, OutputStream outputStream)97 static void writeFormattedLong(final long value, OutputStream outputStream) throws IOException { 98 long y = value; 99 if (y < 0) { 100 y = (-y) | NEGATIVE_MASK; 101 } 102 103 for (int i = 0; i < 8; ++i) { 104 outputStream.write((byte) (y & 0xff)); 105 y >>>= 8; 106 } 107 } 108 109 /** 110 * Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from 111 * the specified {@link InputStream}. 112 * @param inputStream the stream to read from 113 */ readFormattedLong(InputStream inputStream)114 static long readFormattedLong(InputStream inputStream) throws IOException { 115 long result = 0; 116 for (int bitshift = 0; bitshift < 64; bitshift += 8) { 117 result |= ((long) inputStream.read()) << bitshift; 118 } 119 120 if ((result - NEGATIVE_MASK) > 0) { 121 result = (result & ~NEGATIVE_MASK) * -1; 122 } 123 return result; 124 } 125 readBsdiffLong(InputStream in)126 static final long readBsdiffLong(InputStream in) throws PatchFormatException, IOException { 127 long result = 0; 128 for (int bitshift = 0; bitshift < 64; bitshift += 8) { 129 result |= ((long) in.read()) << bitshift; 130 } 131 132 if (result == NEGATIVE_LONG_SIGN_MASK) { 133 // "Negative zero", which is valid in signed-magnitude format. 134 // NB: No sane patch generator should ever produce such a value. 135 throw new PatchFormatException("read negative zero"); 136 } 137 138 if ((result & NEGATIVE_LONG_SIGN_MASK) != 0) { 139 result = -(result & ~NEGATIVE_LONG_SIGN_MASK); 140 } 141 142 return result; 143 } 144 readFully(final InputStream in, final byte[] destination, final int startAt, final int numBytes)145 static void readFully(final InputStream in, final byte[] destination, final int startAt, 146 final int numBytes) throws IOException { 147 int numRead = 0; 148 while (numRead < numBytes) { 149 int readNow = in.read(destination, startAt + numRead, numBytes - numRead); 150 if (readNow == -1) { 151 throw new IOException("truncated input stream"); 152 } 153 numRead += readNow; 154 } 155 } 156 pipe(final InputStream in, final OutputStream out, final byte[] buffer, long copyLength)157 static void pipe(final InputStream in, final OutputStream out, final byte[] buffer, 158 long copyLength) throws IOException { 159 while (copyLength > 0) { 160 int maxCopy = Math.min(buffer.length, (int) copyLength); 161 readFully(in, buffer, 0, maxCopy); 162 out.write(buffer, 0, maxCopy); 163 copyLength -= maxCopy; 164 } 165 } 166 pipe(final RandomAccessFile in, final OutputStream out, final byte[] buffer, long copyLength)167 static void pipe(final RandomAccessFile in, final OutputStream out, final byte[] buffer, 168 long copyLength) throws IOException { 169 while (copyLength > 0) { 170 int maxCopy = Math.min(buffer.length, (int) copyLength); 171 in.readFully(buffer, 0, maxCopy); 172 out.write(buffer, 0, maxCopy); 173 copyLength -= maxCopy; 174 } 175 } 176 fill(byte value, final OutputStream out, final byte[] buffer, long fillLength)177 static void fill(byte value, final OutputStream out, final byte[] buffer, long fillLength) 178 throws IOException { 179 while (fillLength > 0) { 180 int maxCopy = Math.min(buffer.length, (int) fillLength); 181 Arrays.fill(buffer, 0, maxCopy, value); 182 out.write(buffer, 0, maxCopy); 183 fillLength -= maxCopy; 184 } 185 } 186 } 187