blob: bc0c3b852d602df80d75731c809b218d5df7d286 [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.tools.deployer;
import static com.android.tools.deployer.ApkTestUtils.assertApkEntryEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.testutils.TestUtils;
import com.android.tools.deployer.model.Apk;
import com.android.tools.deployer.model.ApkEntry;
import com.android.utils.PathUtils;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.Assert;
import org.junit.Test;
public class ApkParserTest {
private static final String BASE = "tools/base/deploy/deployer/src/test/resource/";
@Test
public void testCentralDirectoryParse() throws IOException {
Path file = TestUtils.resolveWorkspacePath(BASE + "base.apk.remotecd");
byte[] fileContent = Files.readAllBytes(file);
Map<String, ZipUtils.ZipEntry> entries = ZipUtils.readZipEntries(fileContent);
assertEquals(1007, entries.size());
long manifestCrc = entries.get("AndroidManifest.xml").crc;
assertEquals(0x5804A053, manifestCrc);
}
@Test
public void testApkId() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "sample.apk");
Map<String, ApkEntry> files =
new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0).apkEntries;
Map<String, Long> expected = new HashMap<>();
expected.put("META-INF/CERT.SF", 0x45E32198L);
expected.put("AndroidManifest.xml", 0x7BF3141DL);
expected.put("res/layout/main.xml", 0x45FED99AL);
expected.put("META-INF/CERT.RSA", 0x4A3142E9L);
expected.put("resources.arsc", 0x332A4621L);
expected.put("classes.dex", 0x9AC9ECA1L);
expected.put("META-INF/MANIFEST.MF", 0xA93C1C7FL);
String apk = "74eaa38f4d4d8619c7bb886289f84efe1fce7ce3";
assertEquals(7, files.size());
for (String fileName : expected.keySet()) {
assertApkEntryEquals(apk, fileName, expected.get(fileName), files.get(fileName));
}
}
@Test
public void testApkArchiveV2Map() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "v2_signed.apk");
Map<String, ApkEntry> files =
new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0).apkEntries;
Map<String, Long> expected = new HashMap<>();
expected.put(
"res/drawable/abc_list_selector_background_transition_holo_light.xml", 0x29EE1C29L);
expected.put("res/drawable-xxxhdpi-v4/abc_ic_menu_cut_mtrl_alpha.png", 0x566244DBL);
expected.put("res/color/abc_tint_spinner.xml", 0xD7611BC4L);
String apk = "6b1dc4b97ab0dbb66afc33868c700d6f665eeb13";
assertEquals(494, files.size());
// Check a few files
for (String fileName : expected.keySet()) {
assertApkEntryEquals(apk, fileName, expected.get(fileName), files.get(fileName));
}
}
@Test
public void testApkArchiveApkDumpdMatchCrcs() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "signed_app/base.apk");
Map<String, ApkEntry> files =
new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0).apkEntries;
Map<String, Long> expected = new HashMap<>();
expected.put("res/drawable-nodpi-v4/frantic.jpg", 0x492381F1L);
expected.put(
"res/drawable-xxhdpi-v4/abc_textfield_search_default_mtrl_alpha.9.png",
0x4034A6D4L);
expected.put("resources.arsc", 0xFCD1856L);
String apk = "b236acae47f2b2163e9617021c4e1adc7a0c197b";
assertEquals(277, files.size());
// Check a few files
for (String fileName : expected.keySet()) {
assertApkEntryEquals(apk, fileName, expected.get(fileName), files.get(fileName));
}
}
@Test
public void testApkArchiveApkNonV2SignedDumpdMatchDigest() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "nonsigned_app/base.apk");
Map<String, ApkEntry> files =
new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0).apkEntries;
Map<String, Long> expected = new HashMap<>();
expected.put(
"res/drawable/abc_list_selector_background_transition_holo_light.xml", 0x29EE1C29L);
expected.put("res/drawable-xxxhdpi-v4/abc_ic_menu_cut_mtrl_alpha.png", 0x566244DBL);
expected.put("res/color/abc_tint_spinner.xml", 0xD7611BC4L);
String apk = "e5c64a6b8f51198331aefcb7ff695e7faebbd80a";
assertEquals(494, files.size());
// Check a few files
for (String fileName : expected.keySet()) {
assertApkEntryEquals(apk, fileName, expected.get(fileName), files.get(fileName));
}
}
@Test
public void testGetApkInstrumentation() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "instrument.apk");
Apk apk = new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0);
assertEquals("com.example.android.basicgesturedetect.test", apk.packageName);
assertEquals(1, apk.targetPackages.size());
assertEquals("com.example.android.basicgesturedetect", apk.targetPackages.get(0));
}
@Test
public void testGetApkNativeLibs() throws Exception {
Path file = TestUtils.resolveWorkspacePath(BASE + "native_libs.apk");
Apk apk = new ApkParser().parsePaths(ImmutableList.of(file.toString())).get(0);
assertEquals(4, apk.libraryAbis.size());
String[] allExpected = new String[] {"x86", "x86_64", "armeabi-v7a", "arm64-v8a"};
for (String expected : allExpected) {
assertTrue(apk.libraryAbis.contains(expected));
}
}
@Test
public void testFindCDSigned() throws Exception {
ApkParser.ApkArchiveMap map = new ApkParser.ApkArchiveMap();
try (RandomAccessFile file = new RandomAccessFile(BASE + "signed_app/base.apk", "r")) {
ApkParser.findCDLocation(file.getChannel(), map);
assertEquals(
"CD of signed_app.apk found",
true,
map.cdOffset != ApkParser.ApkArchiveMap.UNINITIALIZED);
}
}
@Test
public void testFindCDUnsigned() throws Exception {
ApkParser.ApkArchiveMap map = new ApkParser.ApkArchiveMap();
try (RandomAccessFile file = new RandomAccessFile(BASE + "nonsigned_app/base.apk", "r")) {
ApkParser.findCDLocation(file.getChannel(), map);
assertEquals(
"CD of signed_app.apk found",
true,
map.cdOffset != ApkParser.ApkArchiveMap.UNINITIALIZED);
}
}
@Test
public void testFindSignatureBlock() throws Exception {
ApkParser.ApkArchiveMap map = new ApkParser.ApkArchiveMap();
try (RandomAccessFile file = new RandomAccessFile(BASE + "signed_app/base.apk", "r")) {
ApkParser.findCDLocation(file.getChannel(), map);
ApkParser.findSignatureLocation(file.getChannel(), map);
assertEquals(
"Signature block of signed_app.apk found",
true,
map.signatureBlockOffset != ApkParser.ApkArchiveMap.UNINITIALIZED);
}
}
static void createZip(long numFiles, int sizePerFile, File file) throws IOException {
if (file.exists()) {
file.delete();
}
long fileId = 0;
Random random = new Random(1);
try (FileOutputStream f = new FileOutputStream(file);
ZipOutputStream s = new ZipOutputStream(f)) {
s.setLevel(ZipOutputStream.STORED);
for (int i = 0; i < numFiles; i++) {
long id = fileId++;
String name = String.format("file%06d", id);
ZipEntry entry = new ZipEntry(name);
byte[] bytes = new byte[sizePerFile];
random.nextBytes(bytes);
s.putNextEntry(entry);
s.write(bytes);
s.closeEntry();
}
}
}
@Test
public void testParsingBigZip() throws Exception {
Path tempDirectory = Files.createTempDirectory("");
try {
Path zipArchive = tempDirectory.resolve("big.zip");
int numFiles = 3;
int sizePerFile = 1_000_000_000;
createZip(numFiles, sizePerFile, zipArchive.toFile());
Assert.assertTrue(
"Zip is less than 3GiB", Files.size(zipArchive) > numFiles * sizePerFile);
ApkParser.ApkArchiveMap map = new ApkParser.ApkArchiveMap();
try (RandomAccessFile file = new RandomAccessFile(zipArchive.toFile(), "r")) {
ApkParser.findCDLocation(file.getChannel(), map);
assertEquals(
"Central directory offset found",
true,
map.cdOffset != ApkParser.ApkArchiveMap.UNINITIALIZED);
}
} finally {
PathUtils.deleteRecursivelyIfExists(tempDirectory);
}
}
@Test
public void testUIntOverflow() {
long doesNotFitInUint32 = 0x1_FF_FF_FF_FFL;
boolean exceptionCaught = false;
try {
ZipUtils.longToUint(doesNotFitInUint32);
} catch (IllegalStateException e) {
exceptionCaught = true;
}
Assert.assertTrue("Integer overflow not detected", exceptionCaught);
}
@Test
public void testUIntNoOverflow() {
long doesFitInUint32 = 0xFF_FF_FF_FFL;
boolean exceptionCaught = false;
try {
ZipUtils.longToUint(doesFitInUint32);
} catch (IllegalStateException e) {
exceptionCaught = true;
}
Assert.assertFalse("Integer overflow thrown", exceptionCaught);
}
@Test
public void testUShortOverflow() {
int doesNotFitInUint16 = 0x1_FF_FF;
boolean exceptionCaught = false;
try {
ZipUtils.intToUShort(doesNotFitInUint16);
} catch (IllegalStateException e) {
exceptionCaught = true;
}
Assert.assertTrue("Short overflow not detected", exceptionCaught);
}
@Test
public void testUShortNoOverflow() {
int doesFitInUint16 = 0xFF_FF;
boolean exceptionCaught = false;
try {
ZipUtils.intToUShort(doesFitInUint16);
} catch (IllegalStateException e) {
exceptionCaught = true;
}
Assert.assertFalse("Short overflow thrown", exceptionCaught);
}
@Test
public void testGracefulMissingManifest() throws IOException, DeployerException {
Path tempDirectory = Files.createTempDirectory("");
try {
Path zip = tempDirectory.resolve("testGracefulMissingManifest.zip");
int numFiles = 1;
int sizePerFile = 1;
createZip(numFiles, sizePerFile, zip.toFile());
ApkParser parser = new ApkParser();
parser.parse(zip.toString());
Assert.fail("No exception thrown in apk missing AndroidManifest.xml");
} catch (IOException e) {
Assert.assertTrue(e.getMessage().startsWith(ApkParser.NO_MANIFEST_MSG));
}
}
}