blob: f0f00e16c8927325b9118f9e9a149a53865b5027 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.fastdeploy;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.android.tools.build.apkzlib.zip.ZFile;
import com.android.tools.build.apkzlib.zip.ZFileOptions;
import com.android.tools.build.apkzlib.zip.StoredEntry;
import com.android.tools.build.apkzlib.zip.StoredEntryType;
import com.android.tools.build.apkzlib.zip.CentralDirectoryHeaderCompressInfo;
import com.android.tools.build.apkzlib.zip.CentralDirectoryHeader;
import com.android.fastdeploy.APKMetaData;
import com.android.fastdeploy.APKEntry;
class PatchUtils {
private static final long NEGATIVE_MASK = 1L << 63;
private static final long NEGATIVE_LONG_SIGN_MASK = 1L << 63;
public static final String SIGNATURE = "HAMADI/IHD";
private static long getOffsetFromEntry(StoredEntry entry) {
return entry.getCentralDirectoryHeader().getOffset() + entry.getLocalHeaderSize();
}
public static APKMetaData getAPKMetaData(File apkFile) throws IOException {
APKMetaData.Builder apkEntriesBuilder = APKMetaData.newBuilder();
ZFileOptions options = new ZFileOptions();
ZFile zFile = new ZFile(apkFile, options);
ArrayList<StoredEntry> metaDataEntries = new ArrayList<StoredEntry>();
for (StoredEntry entry : zFile.entries()) {
if (entry.getType() != StoredEntryType.FILE) {
continue;
}
metaDataEntries.add(entry);
}
Collections.sort(metaDataEntries, new Comparator<StoredEntry>() {
private long getOffsetFromEntry(StoredEntry entry) {
return PatchUtils.getOffsetFromEntry(entry);
}
@Override
public int compare(StoredEntry lhs, StoredEntry rhs) {
// -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
return Long.compare(getOffsetFromEntry(lhs), getOffsetFromEntry(rhs));
}
});
for (StoredEntry entry : metaDataEntries) {
CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader();
CentralDirectoryHeaderCompressInfo cdhci = cdh.getCompressionInfoWithWait();
APKEntry.Builder entryBuilder = APKEntry.newBuilder();
entryBuilder.setCrc32(cdh.getCrc32());
entryBuilder.setFileName(cdh.getName());
entryBuilder.setCompressedSize(cdhci.getCompressedSize());
entryBuilder.setUncompressedSize(cdh.getUncompressedSize());
entryBuilder.setDataOffset(getOffsetFromEntry(entry));
apkEntriesBuilder.addEntries(entryBuilder);
apkEntriesBuilder.build();
}
return apkEntriesBuilder.build();
}
/**
* Writes a 64-bit signed integer to the specified {@link OutputStream}. The least significant
* byte is written first and the most significant byte is written last.
* @param value the value to write
* @param outputStream the stream to write to
*/
static void writeFormattedLong(final long value, OutputStream outputStream) throws IOException {
long y = value;
if (y < 0) {
y = (-y) | NEGATIVE_MASK;
}
for (int i = 0; i < 8; ++i) {
outputStream.write((byte) (y & 0xff));
y >>>= 8;
}
}
/**
* Reads a 64-bit signed integer written by {@link #writeFormattedLong(long, OutputStream)} from
* the specified {@link InputStream}.
* @param inputStream the stream to read from
*/
static long readFormattedLong(InputStream inputStream) throws IOException {
long result = 0;
for (int bitshift = 0; bitshift < 64; bitshift += 8) {
result |= ((long) inputStream.read()) << bitshift;
}
if ((result - NEGATIVE_MASK) > 0) {
result = (result & ~NEGATIVE_MASK) * -1;
}
return result;
}
static final long readBsdiffLong(InputStream in) throws PatchFormatException, IOException {
long result = 0;
for (int bitshift = 0; bitshift < 64; bitshift += 8) {
result |= ((long) in.read()) << bitshift;
}
if (result == NEGATIVE_LONG_SIGN_MASK) {
// "Negative zero", which is valid in signed-magnitude format.
// NB: No sane patch generator should ever produce such a value.
throw new PatchFormatException("read negative zero");
}
if ((result & NEGATIVE_LONG_SIGN_MASK) != 0) {
result = -(result & ~NEGATIVE_LONG_SIGN_MASK);
}
return result;
}
static void readFully(final InputStream in, final byte[] destination, final int startAt,
final int numBytes) throws IOException {
int numRead = 0;
while (numRead < numBytes) {
int readNow = in.read(destination, startAt + numRead, numBytes - numRead);
if (readNow == -1) {
throw new IOException("truncated input stream");
}
numRead += readNow;
}
}
static void pipe(final InputStream in, final OutputStream out, final byte[] buffer,
long copyLength) throws IOException {
while (copyLength > 0) {
int maxCopy = Math.min(buffer.length, (int) copyLength);
readFully(in, buffer, 0, maxCopy);
out.write(buffer, 0, maxCopy);
copyLength -= maxCopy;
}
}
static void pipe(final RandomAccessFile in, final OutputStream out, final byte[] buffer,
long copyLength) throws IOException {
while (copyLength > 0) {
int maxCopy = Math.min(buffer.length, (int) copyLength);
in.readFully(buffer, 0, maxCopy);
out.write(buffer, 0, maxCopy);
copyLength -= maxCopy;
}
}
static void fill(byte value, final OutputStream out, final byte[] buffer, long fillLength)
throws IOException {
while (fillLength > 0) {
int maxCopy = Math.min(buffer.length, (int) fillLength);
Arrays.fill(buffer, 0, maxCopy, value);
out.write(buffer, 0, maxCopy);
fillLength -= maxCopy;
}
}
}