summaryrefslogtreecommitdiffstats
path: root/tests/component
diff options
context:
space:
mode:
Diffstat (limited to 'tests/component')
-rw-r--r--tests/component/applypatch_test.cpp609
-rw-r--r--tests/component/bootloader_message_test.cpp207
-rw-r--r--tests/component/edify_test.cpp170
-rw-r--r--tests/component/imgdiff_test.cpp585
-rw-r--r--tests/component/install_test.cpp228
-rw-r--r--tests/component/sideload_test.cpp21
-rw-r--r--tests/component/uncrypt_test.cpp192
-rw-r--r--tests/component/updater_test.cpp609
-rw-r--r--tests/component/verifier_test.cpp237
9 files changed, 2461 insertions, 397 deletions
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
index b44ddd17c..5cba68f8a 100644
--- a/tests/component/applypatch_test.cpp
+++ b/tests/component/applypatch_test.cpp
@@ -23,176 +23,155 @@
#include <sys/types.h>
#include <time.h>
+#include <memory>
#include <string>
+#include <vector>
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/test_utils.h>
+#include <openssl/sha.h>
#include "applypatch/applypatch.h"
+#include "applypatch/applypatch_modes.h"
#include "common/test_constants.h"
-#include "openssl/sha.h"
#include "print_sha1.h"
-static const std::string DATA_PATH = getenv("ANDROID_DATA");
-static const std::string TESTDATA_PATH = "/recovery/testdata";
-static const std::string WORK_FS = "/data";
+static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
+ ASSERT_NE(nullptr, sha1);
-static std::string sha1sum(const std::string& fname) {
- uint8_t digest[SHA_DIGEST_LENGTH];
- std::string data;
- android::base::ReadFileToString(fname, &data);
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(fname, &data));
- SHA1((const uint8_t*)data.c_str(), data.size(), digest);
- return print_sha1(digest);
-}
+ if (fsize != nullptr) {
+ *fsize = data.size();
+ }
-static void mangle_file(const std::string& fname) {
- FILE* fh = fopen(&fname[0], "w");
- int r;
- for (int i=0; i < 1024; i++) {
- r = rand();
- fwrite(&r, sizeof(short), 1, fh);
- }
- fclose(fh);
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
+ *sha1 = print_sha1(digest);
}
-static bool file_cmp(std::string& f1, std::string& f2) {
- std::string c1;
- std::string c2;
- android::base::ReadFileToString(f1, &c1);
- android::base::ReadFileToString(f2, &c2);
- return c1 == c2;
+static void mangle_file(const std::string& fname) {
+ std::string content;
+ content.reserve(1024);
+ for (size_t i = 0; i < 1024; i++) {
+ content[i] = rand() % 256;
+ }
+ ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
}
-static std::string from_testdata_base(const std::string fname) {
- return android::base::StringPrintf("%s%s%s/%s",
- &DATA_PATH[0],
- &NATIVE_TEST_PATH[0],
- &TESTDATA_PATH[0],
- &fname[0]);
+static bool file_cmp(const std::string& f1, const std::string& f2) {
+ std::string c1;
+ android::base::ReadFileToString(f1, &c1);
+ std::string c2;
+ android::base::ReadFileToString(f2, &c2);
+ return c1 == c2;
}
class ApplyPatchTest : public ::testing::Test {
- public:
- static void SetUpTestCase() {
- // set up files
- old_file = from_testdata_base("old.file");
- new_file = from_testdata_base("new.file");
- patch_file = from_testdata_base("patch.bsdiff");
- rand_file = "/cache/applypatch_test_rand.file";
- cache_file = "/cache/saved.file";
-
- // write stuff to rand_file
- android::base::WriteStringToFile("hello", rand_file);
-
- // set up SHA constants
- old_sha1 = sha1sum(old_file);
- new_sha1 = sha1sum(new_file);
- srand(time(NULL));
- bad_sha1_a = android::base::StringPrintf("%040x", rand());
- bad_sha1_b = android::base::StringPrintf("%040x", rand());
-
- struct stat st;
- stat(&new_file[0], &st);
- new_size = st.st_size;
- }
-
- static std::string old_file;
- static std::string new_file;
- static std::string rand_file;
- static std::string cache_file;
- static std::string patch_file;
-
- static std::string old_sha1;
- static std::string new_sha1;
- static std::string bad_sha1_a;
- static std::string bad_sha1_b;
-
- static size_t new_size;
+ public:
+ static void SetUpTestCase() {
+ // set up files
+ old_file = from_testdata_base("old.file");
+ new_file = from_testdata_base("new.file");
+ patch_file = from_testdata_base("patch.bsdiff");
+ rand_file = "/cache/applypatch_test_rand.file";
+ cache_file = "/cache/saved.file";
+
+ // write stuff to rand_file
+ ASSERT_TRUE(android::base::WriteStringToFile("hello", rand_file));
+
+ // set up SHA constants
+ sha1sum(old_file, &old_sha1, &old_size);
+ sha1sum(new_file, &new_sha1, &new_size);
+ srand(time(nullptr));
+ bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ bad_sha1_b = android::base::StringPrintf("%040x", rand());
+ }
+
+ static std::string old_file;
+ static std::string new_file;
+ static std::string rand_file;
+ static std::string cache_file;
+ static std::string patch_file;
+
+ static std::string old_sha1;
+ static std::string new_sha1;
+ static std::string bad_sha1_a;
+ static std::string bad_sha1_b;
+
+ static size_t old_size;
+ static size_t new_size;
};
std::string ApplyPatchTest::old_file;
std::string ApplyPatchTest::new_file;
-static void cp(std::string src, std::string tgt) {
- std::string cmd = android::base::StringPrintf("cp %s %s",
- &src[0],
- &tgt[0]);
- system(&cmd[0]);
+static void cp(const std::string& src, const std::string& tgt) {
+ std::string cmd = "cp " + src + " " + tgt;
+ system(cmd.c_str());
}
static void backup_old() {
- cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
+ cp(ApplyPatchTest::old_file, ApplyPatchTest::cache_file);
}
static void restore_old() {
- cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
+ cp(ApplyPatchTest::cache_file, ApplyPatchTest::old_file);
}
class ApplyPatchCacheTest : public ApplyPatchTest {
- public:
- virtual void SetUp() {
- backup_old();
- }
-
- virtual void TearDown() {
- restore_old();
- }
+ public:
+ virtual void SetUp() {
+ backup_old();
+ }
+
+ virtual void TearDown() {
+ restore_old();
+ }
};
class ApplyPatchFullTest : public ApplyPatchCacheTest {
- public:
- static void SetUpTestCase() {
- ApplyPatchTest::SetUpTestCase();
- unsigned long free_kb = FreeSpaceForFile(&WORK_FS[0]);
- ASSERT_GE(free_kb * 1024, new_size * 3 / 2);
- output_f = new TemporaryFile();
- output_loc = std::string(output_f->path);
-
- struct FileContents fc;
-
- ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
- Value* patch1 = new Value();
- patch1->type = VAL_BLOB;
- patch1->size = fc.data.size();
- patch1->data = static_cast<char*>(malloc(fc.data.size()));
- memcpy(patch1->data, fc.data.data(), fc.data.size());
- patches.push_back(patch1);
-
- ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
- Value* patch2 = new Value();
- patch2->type = VAL_BLOB;
- patch2->size = fc.st.st_size;
- patch2->data = static_cast<char*>(malloc(fc.data.size()));
- memcpy(patch2->data, fc.data.data(), fc.data.size());
- patches.push_back(patch2);
- }
- static void TearDownTestCase() {
- delete output_f;
- for (auto it = patches.begin(); it != patches.end(); ++it) {
- free((*it)->data);
- delete *it;
- }
- patches.clear();
- }
-
- static std::vector<Value*> patches;
- static TemporaryFile* output_f;
- static std::string output_loc;
+ public:
+ static void SetUpTestCase() {
+ ApplyPatchTest::SetUpTestCase();
+
+ output_f = new TemporaryFile();
+ output_loc = std::string(output_f->path);
+
+ struct FileContents fc;
+
+ ASSERT_EQ(0, LoadFileContents(&rand_file[0], &fc));
+ patches.push_back(
+ std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
+
+ ASSERT_EQ(0, LoadFileContents(&patch_file[0], &fc));
+ patches.push_back(
+ std::make_unique<Value>(VAL_BLOB, std::string(fc.data.begin(), fc.data.end())));
+ }
+
+ static void TearDownTestCase() {
+ delete output_f;
+ patches.clear();
+ }
+
+ static std::vector<std::unique_ptr<Value>> patches;
+ static TemporaryFile* output_f;
+ static std::string output_loc;
};
class ApplyPatchDoubleCacheTest : public ApplyPatchFullTest {
- public:
- virtual void SetUp() {
- ApplyPatchCacheTest::SetUp();
- cp(cache_file, "/cache/reallysaved.file");
- }
-
- virtual void TearDown() {
- cp("/cache/reallysaved.file", cache_file);
- ApplyPatchCacheTest::TearDown();
- }
+ public:
+ virtual void SetUp() {
+ ApplyPatchCacheTest::SetUp();
+ cp(cache_file, "/cache/reallysaved.file");
+ }
+
+ virtual void TearDown() {
+ cp("/cache/reallysaved.file", cache_file);
+ ApplyPatchCacheTest::TearDown();
+ }
};
std::string ApplyPatchTest::rand_file;
@@ -202,191 +181,263 @@ std::string ApplyPatchTest::old_sha1;
std::string ApplyPatchTest::new_sha1;
std::string ApplyPatchTest::bad_sha1_a;
std::string ApplyPatchTest::bad_sha1_b;
-
+size_t ApplyPatchTest::old_size;
size_t ApplyPatchTest::new_size;
-std::vector<Value*> ApplyPatchFullTest::patches;
+std::vector<std::unique_ptr<Value>> ApplyPatchFullTest::patches;
TemporaryFile* ApplyPatchFullTest::output_f;
std::string ApplyPatchFullTest::output_loc;
+TEST_F(ApplyPatchTest, CheckModeSkip) {
+ std::vector<std::string> sha1s;
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
+}
+
TEST_F(ApplyPatchTest, CheckModeSingle) {
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchTest, CheckModeMultiple) {
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchTest, CheckModeFailure) {
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+}
+
+TEST_F(ApplyPatchTest, CheckModeEmmcTarget) {
+ // EMMC:old_file:size:sha1 should pass the check.
+ std::string src_file =
+ "EMMC:" + old_file + ":" + std::to_string(old_size) + ":" + old_sha1;
+ std::vector<std::string> sha1s;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size-1):sha1:(size+1):sha1 should fail the check.
+ src_file = "EMMC:" + old_file + ":" + std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1;
+ ASSERT_EQ(1, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+ src_file = "EMMC:" + old_file + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1 + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:old_file:(size+1):sha1:(size-1):sha1:size:sha1 should pass the check.
+ src_file = "EMMC:" + old_file + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
+
+ // EMMC:new_file:(size+1):old_sha1:(size-1):old_sha1:size:old_sha1:size:new_sha1
+ // should pass the check.
+ src_file = "EMMC:" + new_file + ":" +
+ std::to_string(old_size + 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size - 1) + ":" + old_sha1 + ":" +
+ std::to_string(old_size) + ":" + old_sha1 + ":" +
+ std::to_string(new_size) + ":" + new_sha1;
+ ASSERT_EQ(0, applypatch_check(src_file.c_str(), sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSingle) {
- mangle_file(old_file);
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedMultiple) {
- mangle_file(old_file);
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedFailure) {
- mangle_file(old_file);
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ mangle_file(old_file);
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingSingle) {
- unlink(&old_file[0]);
- char* s = &old_sha1[0];
- ASSERT_EQ(0, applypatch_check(&old_file[0], 1, &s));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { old_sha1 };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingMultiple) {
- unlink(&old_file[0]);
- char* argv[3] = {
- &bad_sha1_a[0],
- &old_sha1[0],
- &bad_sha1_b[0]
- };
- ASSERT_EQ(0, applypatch_check(&old_file[0], 3, argv));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { bad_sha1_a, old_sha1, bad_sha1_b };
+ ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
}
TEST_F(ApplyPatchCacheTest, CheckCacheMissingFailure) {
- unlink(&old_file[0]);
- char* argv[2] = {
- &bad_sha1_a[0],
- &bad_sha1_b[0]
- };
- ASSERT_NE(0, applypatch_check(&old_file[0], 2, argv));
+ unlink(&old_file[0]);
+ std::vector<std::string> sha1s = { bad_sha1_a, bad_sha1_b };
+ ASSERT_NE(0, applypatch_check(&old_file[0], sha1s));
+}
+
+TEST(ApplyPatchModesTest, InvalidArgs) {
+ // At least two args (including the filename).
+ ASSERT_EQ(2, applypatch_modes(1, (const char* []){ "applypatch" }));
+
+ // Unrecognized args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-x" }));
}
-TEST_F(ApplyPatchFullTest, ApplyInPlace) {
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
-
- int ap_result = applypatch(&old_file[0],
- "-",
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(old_file, new_file));
- // reapply, applypatch is idempotent so it should succeed
- ap_result = applypatch(&old_file[0],
- "-",
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(old_file, new_file));
+TEST(ApplyPatchModesTest, PatchModeEmmcTarget) {
+ std::string boot_img = from_testdata_base("boot.img");
+ size_t boot_img_size;
+ std::string boot_img_sha1;
+ sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+ std::string recovery_img = from_testdata_base("recovery.img");
+ size_t size;
+ std::string recovery_img_sha1;
+ sha1sum(recovery_img, &recovery_img_sha1, &size);
+ std::string recovery_img_size = std::to_string(size);
+
+ std::string bonus_file = from_testdata_base("bonus.file");
+
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+ TemporaryFile tmp1;
+ std::string src_file =
+ "EMMC:" + boot_img + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
+ std::string tgt_file = "EMMC:" + std::string(tmp1.path);
+ std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+ std::vector<const char*> args = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
+
+ // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
+ TemporaryFile tmp2;
+ patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ tgt_file = "EMMC:" + std::string(tmp2.path);
+ std::vector<const char*> args2 = {
+ "applypatch",
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args2.size(), args2.data()));
+
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size> \
+ // <src-sha1-fake>:<patch1> <src-sha1>:<patch2>
+ TemporaryFile tmp3;
+ tgt_file = "EMMC:" + std::string(tmp3.path);
+ std::string bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ std::string bad_sha1_b = android::base::StringPrintf("%040x", rand());
+ std::string patch1 = bad_sha1_a + ":" + from_testdata_base("recovery-from-boot.p");
+ std::string patch2 = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot.p");
+ std::string patch3 = bad_sha1_b + ":" + from_testdata_base("recovery-from-boot.p");
+ std::vector<const char*> args3 = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ src_file.c_str(),
+ tgt_file.c_str(),
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ patch1.c_str(),
+ patch2.c_str(),
+ patch3.c_str()
+ };
+ ASSERT_EQ(0, applypatch_modes(args3.size(), args3.data()));
}
-TEST_F(ApplyPatchFullTest, ApplyInNewLocation) {
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, PatchModeInvalidArgs) {
+ // Invalid bonus file.
+ ASSERT_NE(0, applypatch_modes(3, (const char* []){ "applypatch", "-b", "/doesntexist" }));
+
+ std::string bonus_file = from_testdata_base("bonus.file");
+ // With bonus file, but missing args.
+ ASSERT_EQ(2, applypatch_modes(3, (const char* []){ "applypatch", "-b", bonus_file.c_str() }));
+
+ std::string boot_img = from_testdata_base("boot.img");
+ size_t boot_img_size;
+ std::string boot_img_sha1;
+ sha1sum(boot_img, &boot_img_sha1, &boot_img_size);
+
+ std::string recovery_img = from_testdata_base("recovery.img");
+ size_t size;
+ std::string recovery_img_sha1;
+ sha1sum(recovery_img, &recovery_img_sha1, &size);
+ std::string recovery_img_size = std::to_string(size);
+
+ // Bonus file is not supported in flash mode.
+ // applypatch -b <bonus-file> <src-file> <tgt-file> <tgt-sha1> <tgt-size>
+ TemporaryFile tmp4;
+ std::vector<const char*> args4 = {
+ "applypatch",
+ "-b",
+ bonus_file.c_str(),
+ boot_img.c_str(),
+ tmp4.path,
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args4.size(), args4.data()));
+
+ // Failed to parse patch args.
+ TemporaryFile tmp5;
+ std::string bad_arg1 =
+ "invalid-sha1:filename" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ std::vector<const char*> args5 = {
+ "applypatch",
+ boot_img.c_str(),
+ tmp5.path,
+ recovery_img_sha1.c_str(),
+ recovery_img_size.c_str(),
+ bad_arg1.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args5.size(), args5.data()));
+
+ // Target size cannot be zero.
+ TemporaryFile tmp6;
+ std::string patch = boot_img_sha1 + ":" + from_testdata_base("recovery-from-boot-with-bonus.p");
+ std::vector<const char*> args6 = {
+ "applypatch",
+ boot_img.c_str(),
+ tmp6.path,
+ recovery_img_sha1.c_str(),
+ "0", // target size
+ patch.c_str()
+ };
+ ASSERT_NE(0, applypatch_modes(args6.size(), args6.data()));
}
-TEST_F(ApplyPatchFullTest, ApplyCorruptedInNewLocation) {
- mangle_file(old_file);
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_EQ(0, ap_result);
- ASSERT_TRUE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, CheckModeInvalidArgs) {
+ // Insufficient args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
+}
+
+TEST(ApplyPatchModesTest, SpaceModeInvalidArgs) {
+ // Insufficient args.
+ ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-s" }));
+
+ // Invalid bytes arg.
+ ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "x" }));
+
+ // 0 is invalid.
+ ASSERT_EQ(1, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0" }));
+
+ // 0x10 is fine.
+ ASSERT_EQ(0, applypatch_modes(3, (const char* []){ "applypatch", "-s", "0x10" }));
}
-TEST_F(ApplyPatchDoubleCacheTest, ApplyDoubleCorruptedInNewLocation) {
- mangle_file(old_file);
- mangle_file(cache_file);
-
- std::vector<char*> sha1s;
- sha1s.push_back(&bad_sha1_a[0]);
- sha1s.push_back(&old_sha1[0]);
- int ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_NE(0, ap_result);
- ASSERT_FALSE(file_cmp(output_loc, new_file));
- ap_result = applypatch(&old_file[0],
- &output_loc[0],
- &new_sha1[0],
- new_size,
- 2,
- sha1s.data(),
- patches.data(),
- nullptr);
- ASSERT_NE(0, ap_result);
- ASSERT_FALSE(file_cmp(output_loc, new_file));
+TEST(ApplyPatchModesTest, ShowLicenses) {
+ ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
}
diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp
new file mode 100644
index 000000000..0357accfe
--- /dev/null
+++ b/tests/component/bootloader_message_test.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <string>
+#include <vector>
+
+#include <android-base/strings.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+class BootloaderMessageTest : public ::testing::Test {
+ protected:
+ BootloaderMessageTest() : has_misc(true) {}
+
+ virtual void SetUp() override {
+ has_misc = parse_misc();
+ }
+
+ virtual void TearDown() override {
+ // Clear the BCB.
+ if (has_misc) {
+ std::string err;
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+ }
+ }
+
+ bool has_misc;
+};
+
+TEST_F(BootloaderMessageTest, clear_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Clear the BCB.
+ std::string err;
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+
+ // Verify the content.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // All the bytes should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot), '\0'),
+ std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
+
+TEST_F(BootloaderMessageTest, read_and_write_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write the BCB.
+ bootloader_message boot = {};
+ strlcpy(boot.command, "command", sizeof(boot.command));
+ strlcpy(boot.recovery, "message1\nmessage2\n", sizeof(boot.recovery));
+ strlcpy(boot.status, "status1", sizeof(boot.status));
+
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+ ASSERT_EQ(std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)),
+ std::string(reinterpret_cast<const char*>(&boot_verify), sizeof(boot_verify)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write the options to BCB.
+ std::vector<std::string> options = { "option1", "option2" };
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Inject some bytes into boot, which should be overwritten while reading.
+ bootloader_message boot;
+ strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+ strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // Verify that command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_EQ(expected, std::string(boot.recovery));
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_empty) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write empty vector.
+ std::vector<std::string> options;
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ("recovery\n", std::string(boot.recovery));
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, write_bootloader_message_options_long) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Write super long message.
+ std::vector<std::string> options;
+ for (int i = 0; i < 100; i++) {
+ options.push_back("option: " + std::to_string(i));
+ }
+
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(options, &err)) << "Failed to write BCB: " << err;
+
+ // Read and verify.
+ bootloader_message boot;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // Make sure it's long enough.
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_GE(expected.size(), sizeof(boot.recovery));
+
+ // command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(expected.substr(0, sizeof(boot.recovery) - 1), std::string(boot.recovery));
+ ASSERT_EQ('\0', boot.recovery[sizeof(boot.recovery) - 1]);
+
+ // The rest should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot.status), '\0'), std::string(boot.status, sizeof(boot.status)));
+ ASSERT_EQ(std::string(sizeof(boot.stage), '\0'), std::string(boot.stage, sizeof(boot.stage)));
+ ASSERT_EQ(std::string(sizeof(boot.reserved), '\0'),
+ std::string(boot.reserved, sizeof(boot.reserved)));
+}
+
+TEST_F(BootloaderMessageTest, update_bootloader_message) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Inject some bytes into boot, which should be not overwritten later.
+ bootloader_message boot;
+ strlcpy(boot.recovery, "random message", sizeof(boot.recovery));
+ strlcpy(boot.reserved, "reserved bytes", sizeof(boot.reserved));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message(boot, &err)) << "Failed to write BCB: " << err;
+
+ // Update the BCB message.
+ std::vector<std::string> options = { "option1", "option2" };
+ ASSERT_TRUE(update_bootloader_message(options, &err)) << "Failed to update BCB: " << err;
+
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message(&boot_verify, &err)) << "Failed to read BCB: " << err;
+
+ // Verify that command and recovery fields should be set.
+ ASSERT_EQ("boot-recovery", std::string(boot_verify.command));
+ std::string expected = "recovery\n" + android::base::Join(options, "\n") + "\n";
+ ASSERT_EQ(expected, std::string(boot_verify.recovery));
+
+ // The rest should be intact.
+ ASSERT_EQ(std::string(boot.status), std::string(boot_verify.status));
+ ASSERT_EQ(std::string(boot.stage), std::string(boot_verify.stage));
+ ASSERT_EQ(std::string(boot.reserved), std::string(boot_verify.reserved));
+}
diff --git a/tests/component/edify_test.cpp b/tests/component/edify_test.cpp
new file mode 100644
index 000000000..61a1e6b64
--- /dev/null
+++ b/tests/component/edify_test.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#include <memory>
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "edify/expr.h"
+
+static void expect(const char* expr_str, const char* expected) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ EXPECT_EQ(0, parse_string(expr_str, &e, &error_count));
+ EXPECT_EQ(0, error_count);
+
+ State state(expr_str, nullptr);
+
+ std::string result;
+ bool status = Evaluate(&state, e, &result);
+
+ if (expected == nullptr) {
+ EXPECT_FALSE(status);
+ } else {
+ EXPECT_STREQ(expected, result.c_str());
+ }
+
+}
+
+class EdifyTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ RegisterBuiltins();
+ }
+};
+
+TEST_F(EdifyTest, parsing) {
+ expect("a", "a");
+ expect("\"a\"", "a");
+ expect("\"\\x61\"", "a");
+ expect("# this is a comment\n"
+ " a\n"
+ " \n",
+ "a");
+}
+
+TEST_F(EdifyTest, sequence) {
+ // sequence operator
+ expect("a; b; c", "c");
+}
+
+TEST_F(EdifyTest, concat) {
+ // string concat operator
+ expect("a + b", "ab");
+ expect("a + \n \"b\"", "ab");
+ expect("a + b +\nc\n", "abc");
+
+ // string concat function
+ expect("concat(a, b)", "ab");
+ expect("concat(a,\n \"b\")", "ab");
+ expect("concat(a + b,\nc,\"d\")", "abcd");
+ expect("\"concat\"(a + b,\nc,\"d\")", "abcd");
+}
+
+TEST_F(EdifyTest, logical) {
+ // logical and
+ expect("a && b", "b");
+ expect("a && \"\"", "");
+ expect("\"\" && b", "");
+ expect("\"\" && \"\"", "");
+ expect("\"\" && abort()", ""); // test short-circuiting
+ expect("t && abort()", nullptr);
+
+ // logical or
+ expect("a || b", "a");
+ expect("a || \"\"", "a");
+ expect("\"\" || b", "b");
+ expect("\"\" || \"\"", "");
+ expect("a || abort()", "a"); // test short-circuiting
+ expect("\"\" || abort()", NULL);
+
+ // logical not
+ expect("!a", "");
+ expect("! \"\"", "t");
+ expect("!!a", "t");
+}
+
+TEST_F(EdifyTest, precedence) {
+ // precedence
+ expect("\"\" == \"\" && b", "b");
+ expect("a + b == ab", "t");
+ expect("ab == a + b", "t");
+ expect("a + (b == ab)", "a");
+ expect("(ab == a) + b", "b");
+}
+
+TEST_F(EdifyTest, substring) {
+ // substring function
+ expect("is_substring(cad, abracadabra)", "t");
+ expect("is_substring(abrac, abracadabra)", "t");
+ expect("is_substring(dabra, abracadabra)", "t");
+ expect("is_substring(cad, abracxadabra)", "");
+ expect("is_substring(abrac, axbracadabra)", "");
+ expect("is_substring(dabra, abracadabrxa)", "");
+}
+
+TEST_F(EdifyTest, ifelse) {
+ // ifelse function
+ expect("ifelse(t, yes, no)", "yes");
+ expect("ifelse(!t, yes, no)", "no");
+ expect("ifelse(t, yes, abort())", "yes");
+ expect("ifelse(!t, abort(), no)", "no");
+}
+
+TEST_F(EdifyTest, if_statement) {
+ // if "statements"
+ expect("if t then yes else no endif", "yes");
+ expect("if \"\" then yes else no endif", "no");
+ expect("if \"\" then yes endif", "");
+ expect("if \"\"; t then yes endif", "yes");
+}
+
+TEST_F(EdifyTest, comparison) {
+ // numeric comparisons
+ expect("less_than_int(3, 14)", "t");
+ expect("less_than_int(14, 3)", "");
+ expect("less_than_int(x, 3)", "");
+ expect("less_than_int(3, x)", "");
+ expect("greater_than_int(3, 14)", "");
+ expect("greater_than_int(14, 3)", "t");
+ expect("greater_than_int(x, 3)", "");
+ expect("greater_than_int(3, x)", "");
+}
+
+TEST_F(EdifyTest, big_string) {
+ // big string
+ expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str());
+}
+
+TEST_F(EdifyTest, unknown_function) {
+ // unknown function
+ const char* script1 = "unknown_function()";
+ std::unique_ptr<Expr> expr;
+ int error_count = 0;
+ EXPECT_EQ(1, parse_string(script1, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script2 = "abc; unknown_function()";
+ error_count = 0;
+ EXPECT_EQ(1, parse_string(script2, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script3 = "unknown_function1() || yes";
+ error_count = 0;
+ EXPECT_EQ(1, parse_string(script3, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+}
diff --git a/tests/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp
new file mode 100644
index 000000000..2f648501c
--- /dev/null
+++ b/tests/component/imgdiff_test.cpp
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/memory.h>
+#include <android-base/test_utils.h>
+#include <applypatch/imgdiff.h>
+#include <applypatch/imgpatch.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_writer.h>
+
+using android::base::get_unaligned;
+
+static ssize_t MemorySink(const unsigned char* data, ssize_t len, void* token) {
+ std::string* s = static_cast<std::string*>(token);
+ s->append(reinterpret_cast<const char*>(data), len);
+ return len;
+}
+
+// Sanity check for the given imgdiff patch header.
+static void verify_patch_header(const std::string& patch, size_t* num_normal, size_t* num_raw,
+ size_t* num_deflate) {
+ const size_t size = patch.size();
+ const char* data = patch.data();
+
+ ASSERT_GE(size, 12U);
+ ASSERT_EQ("IMGDIFF2", std::string(data, 8));
+
+ const int num_chunks = get_unaligned<int32_t>(data + 8);
+ ASSERT_GE(num_chunks, 0);
+
+ size_t normal = 0;
+ size_t raw = 0;
+ size_t deflate = 0;
+
+ size_t pos = 12;
+ for (int i = 0; i < num_chunks; ++i) {
+ ASSERT_LE(pos + 4, size);
+ int type = get_unaligned<int32_t>(data + pos);
+ pos += 4;
+ if (type == CHUNK_NORMAL) {
+ pos += 24;
+ ASSERT_LE(pos, size);
+ normal++;
+ } else if (type == CHUNK_RAW) {
+ ASSERT_LE(pos + 4, size);
+ ssize_t data_len = get_unaligned<int32_t>(data + pos);
+ ASSERT_GT(data_len, 0);
+ pos += 4 + data_len;
+ ASSERT_LE(pos, size);
+ raw++;
+ } else if (type == CHUNK_DEFLATE) {
+ pos += 60;
+ ASSERT_LE(pos, size);
+ deflate++;
+ } else {
+ FAIL() << "Invalid patch type: " << type;
+ }
+ }
+
+ if (num_normal != nullptr) *num_normal = normal;
+ if (num_raw != nullptr) *num_raw = raw;
+ if (num_deflate != nullptr) *num_deflate = deflate;
+}
+
+TEST(ImgdiffTest, invalid_args) {
+ // Insufficient inputs.
+ ASSERT_EQ(2, imgdiff(1, (const char* []){ "imgdiff" }));
+ ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-z" }));
+ ASSERT_EQ(2, imgdiff(2, (const char* []){ "imgdiff", "-b" }));
+ ASSERT_EQ(2, imgdiff(3, (const char* []){ "imgdiff", "-z", "-b" }));
+
+ // Failed to read bonus file.
+ ASSERT_EQ(1, imgdiff(3, (const char* []){ "imgdiff", "-b", "doesntexist" }));
+
+ // Failed to read input files.
+ ASSERT_EQ(1, imgdiff(4, (const char* []){ "imgdiff", "doesntexist", "doesntexist", "output" }));
+ ASSERT_EQ(
+ 1, imgdiff(5, (const char* []){ "imgdiff", "-z", "doesntexist", "doesntexist", "output" }));
+}
+
+TEST(ImgdiffTest, image_mode_smoke) {
+ // Random bytes.
+ const std::string src("abcdefg");
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ const std::string tgt("abcdefgxyz");
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_store) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", 0)); // Store mode.
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", 0)); // Store mode.
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
+ // Construct src and tgt zip files.
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.fd, "wb");
+ ZipWriter src_writer(src_file_ptr);
+ ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string src_content("abcdefg");
+ ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
+ ASSERT_EQ(0, src_writer.FinishEntry());
+ ASSERT_EQ(0, src_writer.Finish());
+ ASSERT_EQ(0, fclose(src_file_ptr));
+
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.fd, "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content("abcdefgxyz");
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+ // Add trailing zeros to the target zip file.
+ std::vector<uint8_t> zeros(10);
+ ASSERT_EQ(zeros.size(), fwrite(zeros.data(), sizeof(uint8_t), zeros.size(), tgt_file_ptr));
+ ASSERT_EQ(0, fclose(tgt_file_ptr));
+
+ // Compute patch.
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", "-z", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string tgt;
+ ASSERT_TRUE(android::base::ReadFileToString(tgt_file.path, &tgt));
+ std::string src;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file.path, &src));
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_simple) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+ '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+ '\x00', '\x00', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+ const std::vector<char> tgt_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
+ '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+ '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_different_num_chunks) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd) + gzipped "test".
+ const std::vector<char> src_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '\x1f', '\x8b', '\x08',
+ '\x00', '\xc4', '\x1e', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac', '\x02',
+ '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03', '\x00', '\x00', '\x00', '\x1f', '\x8b',
+ '\x08', '\x00', '\xb2', '\x3a', '\x53', '\x58', '\x00', '\x03', '\x2b', '\x49', '\x2d',
+ '\x2e', '\x01', '\x00', '\x0c', '\x7e', '\x7f', '\xd8', '\x04', '\x00', '\x00', '\x00'
+ };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + gzipped "xxyyzz".
+ const std::vector<char> tgt_data = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z', '\x1f', '\x8b',
+ '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xa8', '\xac',
+ '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00', '\x00', '\x00'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(1, imgdiff(args.size(), args.data()));
+}
+
+TEST(ImgdiffTest, image_mode_merge_chunks) {
+ // src: "abcdefgh" + gzipped "xyz" (echo -n "xyz" | gzip -f | hd).
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8', '\xac',
+ '\x02', '\x00', '\x67', '\xba', '\x8e', '\xeb', '\x03',
+ '\x00', '\x00', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: gzipped "xyz" + "abcdefgh".
+ const std::vector<char> tgt_data = {
+ '\x1f', '\x8b', '\x08', '\x00', '\x62', '\x1f', '\x53', '\x58', '\x00', '\x03', '\xab', '\xa8',
+ '\xa8', '\xac', '\xac', '\xaa', '\x02', '\x00', '\x96', '\x30', '\x06', '\xb7', '\x06', '\x00',
+ '\x00', '\x00', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z'
+ };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ // Since a gzipped entry will become CHUNK_RAW (header) + CHUNK_DEFLATE (data) +
+ // CHUNK_RAW (footer), they both should contain the same chunk types after merging.
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect three entries: CHUNK_RAW (header) + CHUNK_DEFLATE (data) + CHUNK_RAW (footer).
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(1U, num_deflate);
+ ASSERT_EQ(2U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_spurious_magic) {
+ // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', 't', 'e', 's', 't' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input1) {
+ // src: "abcdefgh" + '0x1f8b0b'.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', '\x1f', '\x8b', '\x08' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_short_input2) {
+ // src: "abcdefgh" + '0x1f8b0b00'.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', '\x1f', '\x8b', '\x08', '\x00' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz".
+ const std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_RAW (header) entry.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(0U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(1U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
+
+TEST(ImgdiffTest, image_mode_single_entry_long) {
+ // src: "abcdefgh" + '0x1f8b0b00' + some bytes.
+ const std::vector<char> src_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', '\x1f', '\x8b', '\x08', '\x00', '\xc4', '\x1e',
+ '\x53', '\x58', 't', 'e', 's', 't' };
+ const std::string src(src_data.cbegin(), src_data.cend());
+ TemporaryFile src_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src, src_file.path));
+
+ // tgt: "abcdefgxyz" + 200 bytes.
+ std::vector<char> tgt_data = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'x', 'y', 'z' };
+ tgt_data.resize(tgt_data.size() + 200);
+
+ const std::string tgt(tgt_data.cbegin(), tgt_data.cend());
+ TemporaryFile tgt_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
+
+ TemporaryFile patch_file;
+ std::vector<const char*> args = {
+ "imgdiff", src_file.path, tgt_file.path, patch_file.path,
+ };
+ ASSERT_EQ(0, imgdiff(args.size(), args.data()));
+
+ // Verify.
+ std::string patch;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch));
+
+ // Expect one CHUNK_NORMAL entry, since it's exceeding the 160-byte limit for RAW.
+ size_t num_normal;
+ size_t num_raw;
+ size_t num_deflate;
+ verify_patch_header(patch, &num_normal, &num_raw, &num_deflate);
+ ASSERT_EQ(1U, num_normal);
+ ASSERT_EQ(0U, num_deflate);
+ ASSERT_EQ(0U, num_raw);
+
+ std::string patched;
+ ASSERT_EQ(0, ApplyImagePatch(reinterpret_cast<const unsigned char*>(src.data()), src.size(),
+ reinterpret_cast<const unsigned char*>(patch.data()), patch.size(),
+ MemorySink, &patched));
+ ASSERT_EQ(tgt, patched);
+}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
new file mode 100644
index 000000000..a5c0c1025
--- /dev/null
+++ b/tests/component/install_test.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2017 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 agree 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.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "install.h"
+#include "private/install.h"
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ // The archive must have something to be opened correctly.
+ ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ // Doesn't contain compatibility zip entry.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ASSERT_TRUE(verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_invalid_entry) {
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", 0));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ // Empty compatibility zip entry.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ASSERT_FALSE(verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
+ TemporaryFile compatibility_zip_file;
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ ZipWriter compatibility_zip_writer(compatibility_zip);
+ ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+ std::string malformed_xml = "malformed";
+ ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(malformed_xml.data(), malformed_xml.size()));
+ ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+ ASSERT_EQ(0, compatibility_zip_writer.Finish());
+ ASSERT_EQ(0, fclose(compatibility_zip));
+
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ ASSERT_EQ(0,
+ writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::vector<std::string> compatibility_info;
+ compatibility_info.push_back(malformed_xml);
+ // Malformed compatibility zip is expected to be rejected by libvintf. But we defer that to
+ // libvintf.
+ std::string err;
+ bool result =
+ android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+ ASSERT_EQ(result, verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml) {
+ static constexpr const char* system_manifest_xml_path = "/system/manifest.xml";
+ if (access(system_manifest_xml_path, R_OK) == -1) {
+ GTEST_LOG_(INFO) << "Test skipped on devices w/o /system/manifest.xml.";
+ return;
+ }
+ std::string system_manifest_xml_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(system_manifest_xml_path, &system_manifest_xml_content));
+ TemporaryFile compatibility_zip_file;
+ FILE* compatibility_zip = fdopen(compatibility_zip_file.fd, "w");
+ ZipWriter compatibility_zip_writer(compatibility_zip);
+ ASSERT_EQ(0, compatibility_zip_writer.StartEntry("system_manifest.xml", kCompressDeflated));
+ ASSERT_EQ(0, compatibility_zip_writer.WriteBytes(system_manifest_xml_content.data(),
+ system_manifest_xml_content.size()));
+ ASSERT_EQ(0, compatibility_zip_writer.FinishEntry());
+ ASSERT_EQ(0, compatibility_zip_writer.Finish());
+ ASSERT_EQ(0, fclose(compatibility_zip));
+
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("compatibility.zip", kCompressStored));
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ ASSERT_EQ(0,
+ writer.WriteBytes(compatibility_zip_content.data(), compatibility_zip_content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::vector<std::string> compatibility_info;
+ compatibility_info.push_back(system_manifest_xml_content);
+ std::string err;
+ bool result =
+ android::vintf::VintfObjectRecovery::CheckCompatibility(compatibility_info, &err) == 0;
+ // Make sure the result is consistent with libvintf library.
+ ASSERT_EQ(result, verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, update_binary_command_smoke) {
+#ifdef AB_OTA_UPDATER
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.StartEntry("payload_properties.txt", kCompressStored));
+ const std::string properties = "some_properties";
+ ASSERT_EQ(0, writer.WriteBytes(properties.data(), properties.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ // A metadata entry is mandatory.
+ ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+ std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+ ASSERT_NE("", timestamp);
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+ },
+ "\n");
+ ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string path = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_EQ(0, update_binary_command(path, zip, 0, status_fd, &cmd));
+ ASSERT_EQ("/sbin/update_engine_sideload", cmd[0]);
+ ASSERT_EQ("--payload=file://" + path, cmd[1]);
+ ASSERT_EQ("--headers=" + properties, cmd[3]);
+ ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ CloseArchive(zip);
+#else
+ // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+ GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif // AB_OTA_UPDATER
+}
+
+TEST(InstallTest, update_binary_command_invalid) {
+#ifdef AB_OTA_UPDATER
+ TemporaryFile temp_file;
+ FILE* zip_file = fdopen(temp_file.fd, "w");
+ ZipWriter writer(zip_file);
+ // Missing payload_properties.txt.
+ ASSERT_EQ(0, writer.StartEntry("payload.bin", kCompressStored));
+ ASSERT_EQ(0, writer.FinishEntry());
+ // A metadata entry is mandatory.
+ ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+ std::string timestamp = android::base::GetProperty("ro.build.date.utc", "");
+ ASSERT_NE("", timestamp);
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB", "pre-device=" + device, "post-timestamp=" + timestamp,
+ },
+ "\n");
+ ASSERT_EQ(0, writer.WriteBytes(metadata.data(), metadata.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string path = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(path, zip, 0, status_fd, &cmd));
+ CloseArchive(zip);
+#else
+ // Cannot test update_binary_command() because it tries to extract update-binary to /tmp.
+ GTEST_LOG_(INFO) << "Test skipped on non-A/B device.";
+#endif // AB_OTA_UPDATER
+}
diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp
new file mode 100644
index 000000000..ea93e9b84
--- /dev/null
+++ b/tests/component/sideload_test.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+#include <unistd.h>
+#include <gtest/gtest.h>
+
+TEST(SideloadTest, fusedevice) {
+ ASSERT_NE(-1, access("/dev/fuse", R_OK | W_OK));
+}
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
new file mode 100644
index 000000000..4f2b8164f
--- /dev/null
+++ b/tests/component/uncrypt_test.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+#include "common/component_test_util.h"
+
+static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
+static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
+static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
+static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
+static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+
+class UncryptTest : public ::testing::Test {
+ protected:
+ UncryptTest() : has_misc(true) {}
+
+ virtual void SetUp() override {
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+ std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+ std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+ LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
+ << uncrypt << "]";
+ if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+
+ ASSERT_TRUE(success) << "uncrypt service is not available.";
+
+ has_misc = parse_misc();
+ }
+
+ bool has_misc;
+};
+
+TEST_F(UncryptTest, setup_bcb) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Trigger the setup-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ struct sockaddr_un un = {};
+ un.sun_family = AF_UNIX;
+ strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sockfd);
+
+ // Connect to the uncrypt socket.
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+ ASSERT_TRUE(success);
+
+ // Send out the BCB message.
+ std::string message = "--update_message=abc value";
+ std::string message_in_bcb = "recovery\n--update_message=abc value\n";
+ int length = static_cast<int>(message.size());
+ int length_out = htonl(length);
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
+ << "Failed to write length: " << strerror(errno);
+ ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
+ << "Failed to write message: " << strerror(errno);
+
+ // Check the status code from uncrypt.
+ int status;
+ ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+ ASSERT_EQ(100U, ntohl(status));
+
+ // Ack having received the status code.
+ int code = 0;
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+
+ // Verify the message by reading from BCB directly.
+ bootloader_message boot;
+ std::string err;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ ASSERT_EQ("boot-recovery", std::string(boot.command));
+ ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+ // The rest of the boot.recovery message should be zero'd out.
+ ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+ size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+ ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+ // Clear the BCB.
+ ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+}
+
+TEST_F(UncryptTest, clear_bcb) {
+ if (!has_misc) {
+ GTEST_LOG_(INFO) << "Test skipped due to no /misc partition found on the device.";
+ return;
+ }
+
+ // Trigger the clear-bcb service.
+ ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+
+ // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+ sleep(1);
+
+ struct sockaddr_un un = {};
+ un.sun_family = AF_UNIX;
+ strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ ASSERT_NE(-1, sockfd);
+
+ // Connect to the uncrypt socket.
+ bool success = false;
+ for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+ if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+ success = true;
+ break;
+ }
+ sleep(1);
+ }
+ ASSERT_TRUE(success);
+
+ // Check the status code from uncrypt.
+ int status;
+ ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+ ASSERT_EQ(100U, ntohl(status));
+
+ // Ack having received the status code.
+ int code = 0;
+ ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+ ASSERT_EQ(0, close(sockfd));
+
+ ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+
+ // Verify the content by reading from BCB directly.
+ bootloader_message boot;
+ std::string err;
+ ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+ // All the bytes should be cleared.
+ ASSERT_EQ(std::string(sizeof(boot), '\0'),
+ std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
new file mode 100644
index 000000000..5652ddf46
--- /dev/null
+++ b/tests/component/updater_test.cpp
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <bootloader_message/bootloader_message.h>
+#include <bsdiff.h>
+#include <gtest/gtest.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "error_code.h"
+#include "otautil/SysUtil.h"
+#include "print_sha1.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+
+struct selabel_handle *sehandle = nullptr;
+
+static void expect(const char* expected, const char* expr_str, CauseCode cause_code,
+ UpdaterInfo* info = nullptr) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ ASSERT_EQ(0, parse_string(expr_str, &e, &error_count));
+ ASSERT_EQ(0, error_count);
+
+ State state(expr_str, info);
+
+ std::string result;
+ bool status = Evaluate(&state, e, &result);
+
+ if (expected == nullptr) {
+ ASSERT_FALSE(status);
+ } else {
+ ASSERT_TRUE(status);
+ ASSERT_STREQ(expected, result.c_str());
+ }
+
+ // Error code is set in updater/updater.cpp only, by parsing State.errmsg.
+ ASSERT_EQ(kNoError, state.error_code);
+
+ // Cause code should always be available.
+ ASSERT_EQ(cause_code, state.cause_code);
+}
+
+static std::string get_sha1(const std::string& content) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest);
+ return print_sha1(digest);
+}
+
+class UpdaterTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() override {
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+ }
+};
+
+TEST_F(UpdaterTest, getprop) {
+ expect(android::base::GetProperty("ro.product.device", "").c_str(),
+ "getprop(\"ro.product.device\")",
+ kNoCause);
+
+ expect(android::base::GetProperty("ro.build.fingerprint", "").c_str(),
+ "getprop(\"ro.build.fingerprint\")",
+ kNoCause);
+
+ // getprop() accepts only one parameter.
+ expect(nullptr, "getprop()", kArgsParsingFailure);
+ expect(nullptr, "getprop(\"arg1\", \"arg2\")", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, sha1_check) {
+ // sha1_check(data) returns the SHA-1 of the data.
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf", "sha1_check(\"abcd\")", kNoCause);
+ expect("da39a3ee5e6b4b0d3255bfef95601890afd80709", "sha1_check(\"\")", kNoCause);
+
+ // sha1_check(data, sha1_hex, [sha1_hex, ...]) returns the matched SHA-1.
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+ "sha1_check(\"abcd\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+ kNoCause);
+
+ expect("81fe8bfe87576c3ecb22426f8e57847382917acf",
+ "sha1_check(\"abcd\", \"wrong_sha1\", \"81fe8bfe87576c3ecb22426f8e57847382917acf\")",
+ kNoCause);
+
+ // Or "" if there's no match.
+ expect("",
+ "sha1_check(\"abcd\", \"wrong_sha1\")",
+ kNoCause);
+
+ expect("",
+ "sha1_check(\"abcd\", \"wrong_sha1\", \"wrong_sha2\")",
+ kNoCause);
+
+ // sha1_check() expects at least one argument.
+ expect(nullptr, "sha1_check()", kArgsParsingFailure);
+}
+
+TEST_F(UpdaterTest, apply_patch_check) {
+ // Zero-argument is not valid.
+ expect(nullptr, "apply_patch_check()", kArgsParsingFailure);
+
+ // File not found.
+ expect("", "apply_patch_check(\"/doesntexist\")", kNoCause);
+
+ std::string src_file = from_testdata_base("old.file");
+ std::string src_content;
+ ASSERT_TRUE(android::base::ReadFileToString(src_file, &src_content));
+ size_t src_size = src_content.size();
+ std::string src_hash = get_sha1(src_content);
+
+ // One-argument with EMMC:file:size:sha1 should pass the check.
+ std::string filename = android::base::Join(
+ std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size), src_hash }, ":");
+ std::string cmd = "apply_patch_check(\"" + filename + "\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ // EMMC:file:(size-1):sha1:(size+1):sha1 should fail the check.
+ std::string filename_bad = android::base::Join(
+ std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1), src_hash,
+ std::to_string(src_size + 1), src_hash },
+ ":");
+ cmd = "apply_patch_check(\"" + filename_bad + "\")";
+ expect("", cmd.c_str(), kNoCause);
+
+ // EMMC:file:(size-1):sha1:size:sha1:(size+1):sha1 should pass the check.
+ filename_bad =
+ android::base::Join(std::vector<std::string>{ "EMMC", src_file, std::to_string(src_size - 1),
+ src_hash, std::to_string(src_size), src_hash,
+ std::to_string(src_size + 1), src_hash },
+ ":");
+ cmd = "apply_patch_check(\"" + filename_bad + "\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ // Multiple arguments.
+ cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"wrong_sha2\")";
+ expect("", cmd.c_str(), kNoCause);
+
+ cmd = "apply_patch_check(\"" + filename + "\", \"wrong_sha1\", \"" + src_hash +
+ "\", \"wrong_sha2\")";
+ expect("t", cmd.c_str(), kNoCause);
+
+ cmd = "apply_patch_check(\"" + filename_bad + "\", \"wrong_sha1\", \"" + src_hash +
+ "\", \"wrong_sha2\")";
+ expect("t", cmd.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, file_getprop) {
+ // file_getprop() expects two arguments.
+ expect(nullptr, "file_getprop()", kArgsParsingFailure);
+ expect(nullptr, "file_getprop(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "file_getprop(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // File doesn't exist.
+ expect(nullptr, "file_getprop(\"/doesntexist\", \"key1\")", kFileGetPropFailure);
+
+ // Reject too large files (current limit = 65536).
+ TemporaryFile temp_file1;
+ std::string buffer(65540, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(buffer, temp_file1.path));
+
+ // Read some keys.
+ TemporaryFile temp_file2;
+ std::string content("ro.product.name=tardis\n"
+ "# comment\n\n\n"
+ "ro.product.model\n"
+ "ro.product.board = magic \n");
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file2.path));
+
+ std::string script1("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.name\")");
+ expect("tardis", script1.c_str(), kNoCause);
+
+ std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.board\")");
+ expect("magic", script2.c_str(), kNoCause);
+
+ // No match.
+ std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.wrong\")");
+ expect("", script3.c_str(), kNoCause);
+
+ std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.name=\")");
+ expect("", script4.c_str(), kNoCause);
+
+ std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.nam\")");
+ expect("", script5.c_str(), kNoCause);
+
+ std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.model\")");
+ expect("", script6.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, package_extract_dir) {
+ // package_extract_dir expects 2 arguments.
+ expect(nullptr, "package_extract_dir()", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Need to set up the ziphandle.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+
+ // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", "<dir>").
+ TemporaryDir td;
+ std::string temp_dir(td.path);
+ std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")");
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Verify.
+ std::string data;
+ std::string file_c = temp_dir + "/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+
+ std::string file_d = temp_dir + "/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Modify the contents in order to retry. It's expected to be overwritten.
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_c));
+ ASSERT_TRUE(android::base::WriteStringToFile("random", file_d));
+
+ // Extract again and verify.
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ // Clean up the temp files under td.
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "b/" (with slash) should give the same result.
+ script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(file_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ ASSERT_TRUE(android::base::ReadFileToString(file_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_c.c_str()));
+ ASSERT_EQ(0, unlink(file_d.c_str()));
+
+ // Extracting "" is allowed. The entries will carry the path name.
+ script = "package_extract_dir(\"\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ std::string file_a = temp_dir + "/a.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_a, &data));
+ ASSERT_EQ(kATxtContents, data);
+ std::string file_b = temp_dir + "/b.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b, &data));
+ ASSERT_EQ(kBTxtContents, data);
+ std::string file_b_c = temp_dir + "/b/c.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data));
+ ASSERT_EQ(kCTxtContents, data);
+ std::string file_b_d = temp_dir + "/b/d.txt";
+ ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data));
+ ASSERT_EQ(kDTxtContents, data);
+
+ ASSERT_EQ(0, unlink(file_a.c_str()));
+ ASSERT_EQ(0, unlink(file_b.c_str()));
+ ASSERT_EQ(0, unlink(file_b_c.c_str()));
+ ASSERT_EQ(0, unlink(file_b_d.c_str()));
+ ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str()));
+
+ // Extracting non-existent entry should still give "t".
+ script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Only relative zip_path is allowed.
+ script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // Only absolute dest_path is allowed.
+ script = "package_extract_dir(\"b\", \"path\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ CloseArchive(handle);
+}
+
+// TODO: Test extracting to block device.
+TEST_F(UpdaterTest, package_extract_file) {
+ // package_extract_file expects 1 or 2 arguments.
+ expect(nullptr, "package_extract_file()", kArgsParsingFailure);
+ expect(nullptr, "package_extract_file(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ std::string zip_path = from_testdata_base("ziptest_valid.zip");
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
+
+ // Need to set up the ziphandle.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+
+ // Two-argument version.
+ TemporaryFile temp_file1;
+ std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ // Verify the extracted entry.
+ std::string data;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+ ASSERT_EQ(kATxtContents, data);
+
+ // Now extract another entry to the same location, which should overwrite.
+ script = "package_extract_file(\"b.txt\", \"" + std::string(temp_file1.path) + "\")";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file1.path, &data));
+ ASSERT_EQ(kBTxtContents, data);
+
+ // Missing zip entry. The two-argument version doesn't abort.
+ script = "package_extract_file(\"doesntexist\", \"" + std::string(temp_file1.path) + "\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // Extract to /dev/full should fail.
+ script = "package_extract_file(\"a.txt\", \"/dev/full\")";
+ expect("", script.c_str(), kNoCause, &updater_info);
+
+ // One-argument version.
+ script = "sha1_check(package_extract_file(\"a.txt\"))";
+ expect(kATxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+ script = "sha1_check(package_extract_file(\"b.txt\"))";
+ expect(kBTxtSha1Sum.c_str(), script.c_str(), kNoCause, &updater_info);
+
+ // Missing entry. The one-argument version aborts the evaluation.
+ script = "package_extract_file(\"doesntexist\")";
+ expect(nullptr, script.c_str(), kPackageExtractFileFailure, &updater_info);
+
+ CloseArchive(handle);
+}
+
+TEST_F(UpdaterTest, write_value) {
+ // write_value() expects two arguments.
+ expect(nullptr, "write_value()", kArgsParsingFailure);
+ expect(nullptr, "write_value(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "write_value(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // filename cannot be empty.
+ expect(nullptr, "write_value(\"value\", \"\")", kArgsParsingFailure);
+
+ // Write some value to file.
+ TemporaryFile temp_file;
+ std::string value = "magicvalue";
+ std::string script("write_value(\"" + value + "\", \"" + std::string(temp_file.path) + "\")");
+ expect("t", script.c_str(), kNoCause);
+
+ // Verify the content.
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+ ASSERT_EQ(value, content);
+
+ // Allow writing empty string.
+ script = "write_value(\"\", \"" + std::string(temp_file.path) + "\")";
+ expect("t", script.c_str(), kNoCause);
+
+ // Verify the content.
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &content));
+ ASSERT_EQ("", content);
+
+ // It should fail gracefully when write fails.
+ script = "write_value(\"value\", \"/proc/0/file1\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, get_stage) {
+ // get_stage() expects one argument.
+ expect(nullptr, "get_stage()", kArgsParsingFailure);
+ expect(nullptr, "get_stage(\"arg1\", \"arg2\")", kArgsParsingFailure);
+ expect(nullptr, "get_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Set up a local file as BCB.
+ TemporaryFile tf;
+ std::string temp_file(tf.path);
+ bootloader_message boot;
+ strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+ // Can read the stage value.
+ std::string script("get_stage(\"" + temp_file + "\")");
+ expect("2/3", script.c_str(), kNoCause);
+
+ // Bad BCB path.
+ script = "get_stage(\"doesntexist\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_stage) {
+ // set_stage() expects two arguments.
+ expect(nullptr, "set_stage()", kArgsParsingFailure);
+ expect(nullptr, "set_stage(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "set_stage(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Set up a local file as BCB.
+ TemporaryFile tf;
+ std::string temp_file(tf.path);
+ bootloader_message boot;
+ strlcpy(boot.command, "command", sizeof(boot.command));
+ strlcpy(boot.stage, "2/3", sizeof(boot.stage));
+ std::string err;
+ ASSERT_TRUE(write_bootloader_message_to(boot, temp_file, &err));
+
+ // Write with set_stage().
+ std::string script("set_stage(\"" + temp_file + "\", \"1/3\")");
+ expect(tf.path, script.c_str(), kNoCause);
+
+ // Verify.
+ bootloader_message boot_verify;
+ ASSERT_TRUE(read_bootloader_message_from(&boot_verify, temp_file, &err));
+
+ // Stage should be updated, with command part untouched.
+ ASSERT_STREQ("1/3", boot_verify.stage);
+ ASSERT_STREQ(boot.command, boot_verify.command);
+
+ // Bad BCB path.
+ script = "set_stage(\"doesntexist\", \"1/3\")";
+ expect("", script.c_str(), kNoCause);
+
+ script = "set_stage(\"/dev/full\", \"1/3\")";
+ expect("", script.c_str(), kNoCause);
+}
+
+TEST_F(UpdaterTest, set_progress) {
+ // set_progress() expects one argument.
+ expect(nullptr, "set_progress()", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+ // Invalid progress argument.
+ expect(nullptr, "set_progress(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\"3x+5\")", kArgsParsingFailure);
+ expect(nullptr, "set_progress(\".3.5\")", kArgsParsingFailure);
+
+ TemporaryFile tf;
+ UpdaterInfo updater_info;
+ updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ expect(".52", "set_progress(\".52\")", kNoCause, &updater_info);
+ fflush(updater_info.cmd_pipe);
+
+ std::string cmd;
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+ ASSERT_EQ(android::base::StringPrintf("set_progress %f\n", .52), cmd);
+ // recovery-updater protocol expects 2 tokens ("set_progress <frac>").
+ ASSERT_EQ(2U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, show_progress) {
+ // show_progress() expects two arguments.
+ expect(nullptr, "show_progress()", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"arg1\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure);
+
+ // Invalid progress arguments.
+ expect(nullptr, "show_progress(\"arg1\", \"arg2\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\"3x+5\", \"10\")", kArgsParsingFailure);
+ expect(nullptr, "show_progress(\".3\", \"5a\")", kArgsParsingFailure);
+
+ TemporaryFile tf;
+ UpdaterInfo updater_info;
+ updater_info.cmd_pipe = fdopen(tf.fd, "w");
+ expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_info);
+ fflush(updater_info.cmd_pipe);
+
+ std::string cmd;
+ ASSERT_TRUE(android::base::ReadFileToString(tf.path, &cmd));
+ ASSERT_EQ(android::base::StringPrintf("progress %f %d\n", .52, 10), cmd);
+ // recovery-updater protocol expects 3 tokens ("progress <frac> <secs>").
+ ASSERT_EQ(3U, android::base::Split(cmd, " ").size());
+}
+
+TEST_F(UpdaterTest, block_image_update) {
+ // Create a zip file with new_data and patch_data.
+ TemporaryFile zip_file;
+ FILE* zip_file_ptr = fdopen(zip_file.fd, "wb");
+ ZipWriter zip_writer(zip_file_ptr);
+
+ // Add a dummy new data.
+ ASSERT_EQ(0, zip_writer.StartEntry("new_data", 0));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Generate and add the patch data.
+ std::string src_content = std::string(4096, 'a') + std::string(4096, 'c');
+ std::string tgt_content = std::string(4096, 'b') + std::string(4096, 'd');
+ TemporaryFile patch_file;
+ ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(src_content.data()),
+ src_content.size(), reinterpret_cast<const uint8_t*>(tgt_content.data()),
+ tgt_content.size(), patch_file.path, nullptr));
+ std::string patch_content;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
+ ASSERT_EQ(0, zip_writer.StartEntry("patch_data", 0));
+ ASSERT_EQ(0, zip_writer.WriteBytes(patch_content.data(), patch_content.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Add two transfer lists. The first one contains a bsdiff; and we expect the update to succeed.
+ std::string src_hash = get_sha1(src_content);
+ std::string tgt_hash = get_sha1(tgt_content);
+ std::vector<std::string> transfer_list = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + src_hash + " 2,0,2",
+ android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,2 2 - %s:2,0,2", patch_content.size(),
+ src_hash.c_str(), tgt_hash.c_str(), src_hash.c_str()),
+ "free " + src_hash,
+ };
+ ASSERT_EQ(0, zip_writer.StartEntry("transfer_list", 0));
+ std::string commands = android::base::Join(transfer_list, '\n');
+ ASSERT_EQ(0, zip_writer.WriteBytes(commands.data(), commands.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+
+ // Stash and free some blocks, then fail the 2nd update intentionally.
+ std::vector<std::string> fail_transfer_list = {
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + tgt_hash + " 2,0,2",
+ "free " + tgt_hash,
+ "fail",
+ };
+ ASSERT_EQ(0, zip_writer.StartEntry("fail_transfer_list", 0));
+ std::string fail_commands = android::base::Join(fail_transfer_list, '\n');
+ ASSERT_EQ(0, zip_writer.WriteBytes(fail_commands.data(), fail_commands.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+ ASSERT_EQ(0, zip_writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file_ptr));
+
+ MemMapping map;
+ ASSERT_EQ(0, sysMapFile(zip_file.path, &map));
+ ZipArchiveHandle handle;
+ ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_file.path, &handle));
+
+ // Set up the handler, command_pipe, patch offset & length.
+ UpdaterInfo updater_info;
+ updater_info.package_zip = handle;
+ TemporaryFile temp_pipe;
+ updater_info.cmd_pipe = fopen(temp_pipe.path, "wb");
+ updater_info.package_zip_addr = map.addr;
+ updater_info.package_zip_len = map.length;
+
+ // Execute the commands in the 1st transfer list.
+ TemporaryFile update_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
+ std::string script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("transfer_list"), "new_data", "patch_data"))";
+ expect("t", script.c_str(), kNoCause, &updater_info);
+ // The update_file should be patched correctly.
+ std::string updated_content;
+ ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
+ ASSERT_EQ(tgt_hash, get_sha1(updated_content));
+
+ // Expect the 2nd update to fail, but expect the stashed blocks to be freed.
+ script = "block_image_update(\"" + std::string(update_file.path) +
+ R"(", package_extract_file("fail_transfer_list"), "new_data", "patch_data"))";
+ expect("", script.c_str(), kNoCause, &updater_info);
+ // Updater generates the stash name based on the input file name.
+ std::string name_digest = get_sha1(update_file.path);
+ std::string stash_base = "/cache/recovery/" + name_digest;
+ ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+ ASSERT_EQ(-1, access((stash_base + tgt_hash).c_str(), F_OK));
+ ASSERT_EQ(0, rmdir(stash_base.c_str()));
+
+ ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
+ CloseArchive(handle);
+}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
index 780ff2816..4c0648714 100644
--- a/tests/component/verifier_test.cpp
+++ b/tests/component/verifier_test.cpp
@@ -22,108 +22,36 @@
#include <sys/stat.h>
#include <sys/types.h>
-#include <memory>
#include <string>
#include <vector>
-#include <openssl/sha.h>
-
+#include <android-base/file.h>
#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
-#include "common.h"
#include "common/test_constants.h"
-#include "minzip/SysUtil.h"
-#include "ui.h"
+#include "otautil/SysUtil.h"
#include "verifier.h"
-static const char* DATA_PATH = getenv("ANDROID_DATA");
-static const char* TESTDATA_PATH = "/recovery/testdata/";
-
-RecoveryUI* ui = NULL;
-
-class MockUI : public RecoveryUI {
- void Init() { }
- void SetStage(int, int) { }
- void SetLocale(const char*) { }
- void SetBackground(Icon icon) { }
- void SetSystemUpdateText(bool security_update) { }
-
- void SetProgressType(ProgressType determinate) { }
- void ShowProgress(float portion, float seconds) { }
- void SetProgress(float fraction) { }
-
- void ShowText(bool visible) { }
- bool IsTextVisible() { return false; }
- bool WasTextEverVisible() { return false; }
- void Print(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- }
- void PrintOnScreenOnly(const char* fmt, ...) {
- va_list ap;
- va_start(ap, fmt);
- vfprintf(stderr, fmt, ap);
- va_end(ap);
- }
- void ShowFile(const char*) { }
-
- void StartMenu(const char* const * headers, const char* const * items,
- int initial_selection) { }
- int SelectMenu(int sel) { return 0; }
- void EndMenu() { }
-};
-
-void
-ui_print(const char* format, ...) {
- va_list ap;
- va_start(ap, format);
- vfprintf(stdout, format, ap);
- va_end(ap);
-}
+using namespace std::string_literals;
class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
- public:
- MemMapping memmap;
- std::vector<Certificate> certs;
-
- virtual void SetUp() {
- std::vector<std::string> args = GetParam();
- std::string package =
- android::base::StringPrintf("%s%s%s%s", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH, args[0].c_str());
- if (sysMapFile(package.c_str(), &memmap) != 0) {
- FAIL() << "Failed to mmap " << package << ": " << strerror(errno)
- << "\n";
- }
-
- for (auto it = ++(args.cbegin()); it != args.cend(); ++it) {
- if (it->substr(it->length() - 3, it->length()) == "256") {
- if (certs.empty()) {
- FAIL() << "May only specify -sha256 after key type\n";
- }
- certs.back().hash_len = SHA256_DIGEST_LENGTH;
- } else {
- std::string public_key_file = android::base::StringPrintf(
- "%s%s%stest_key_%s.txt", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH, it->c_str());
- ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
- certs.back().hash_len = SHA_DIGEST_LENGTH;
- }
- }
- if (certs.empty()) {
- std::string public_key_file = android::base::StringPrintf(
- "%s%s%stest_key_e3.txt", DATA_PATH, NATIVE_TEST_PATH,
- TESTDATA_PATH);
- ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
- certs.back().hash_len = SHA_DIGEST_LENGTH;
- }
+ protected:
+ void SetUp() override {
+ std::vector<std::string> args = GetParam();
+ std::string package = from_testdata_base(args[0]);
+ if (sysMapFile(package.c_str(), &memmap) != 0) {
+ FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
}
- static void SetUpTestCase() {
- ui = new MockUI();
+ for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
+ std::string public_key_file = from_testdata_base("testkey_" + *it + ".txt");
+ ASSERT_TRUE(load_keys(public_key_file.c_str(), certs));
}
+ }
+
+ MemMapping memmap;
+ std::vector<Certificate> certs;
};
class VerifierSuccessTest : public VerifierTest {
@@ -132,47 +60,120 @@ class VerifierSuccessTest : public VerifierTest {
class VerifierFailureTest : public VerifierTest {
};
+TEST(VerifierTest, load_keys_multiple_keys) {
+ std::string testkey_v4;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+ std::string testkey_v3;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+ std::string keys = testkey_v4 + "," + testkey_v3 + "," + testkey_v4;
+ TemporaryFile key_file1;
+ ASSERT_TRUE(android::base::WriteStringToFile(keys, key_file1.path));
+ std::vector<Certificate> certs;
+ ASSERT_TRUE(load_keys(key_file1.path, certs));
+ ASSERT_EQ(3U, certs.size());
+}
+
+TEST(VerifierTest, load_keys_invalid_keys) {
+ std::vector<Certificate> certs;
+ ASSERT_FALSE(load_keys("/doesntexist", certs));
+
+ // Empty file.
+ TemporaryFile key_file1;
+ ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+ // Invalid contents.
+ ASSERT_TRUE(android::base::WriteStringToFile("invalid", key_file1.path));
+ ASSERT_FALSE(load_keys(key_file1.path, certs));
+
+ std::string testkey_v4;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v4.txt"), &testkey_v4));
+
+ // Invalid key version: "v4 ..." => "v6 ...".
+ std::string invalid_key2(testkey_v4);
+ invalid_key2[1] = '6';
+ TemporaryFile key_file2;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key2, key_file2.path));
+ ASSERT_FALSE(load_keys(key_file2.path, certs));
+
+ // Invalid key content: inserted extra bytes ",2209831334".
+ std::string invalid_key3(testkey_v4);
+ invalid_key3.insert(invalid_key2.size() - 2, ",2209831334");
+ TemporaryFile key_file3;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key3, key_file3.path));
+ ASSERT_FALSE(load_keys(key_file3.path, certs));
+
+ // Invalid key: the last key must not end with an extra ','.
+ std::string invalid_key4 = testkey_v4 + ",";
+ TemporaryFile key_file4;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key4, key_file4.path));
+ ASSERT_FALSE(load_keys(key_file4.path, certs));
+
+ // Invalid key separator.
+ std::string invalid_key5 = testkey_v4 + ";" + testkey_v4;
+ TemporaryFile key_file5;
+ ASSERT_TRUE(android::base::WriteStringToFile(invalid_key5, key_file5.path));
+ ASSERT_FALSE(load_keys(key_file5.path, certs));
+}
+
+TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
+ std::string testkey_v3;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
+
+ TemporaryFile key_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file.path));
+ std::vector<Certificate> certs;
+ ASSERT_TRUE(load_keys(key_file.path, certs));
+
+ // Signature start is 65535 (0xffff) while comment size is 0 (Bug: 31914369).
+ std::string package = "\x50\x4b\x05\x06"s + std::string(12, '\0') + "\xff\xff\xff\xff\x00\x00"s;
+ ASSERT_EQ(VERIFY_FAILURE, verify_file(reinterpret_cast<const unsigned char*>(package.data()),
+ package.size(), certs));
+}
+
TEST_P(VerifierSuccessTest, VerifySucceed) {
- ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_SUCCESS);
+ ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_SUCCESS);
}
TEST_P(VerifierFailureTest, VerifyFailure) {
- ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs), VERIFY_FAILURE);
+ ASSERT_EQ(verify_file(memmap.addr, memmap.length, certs, nullptr), VERIFY_FAILURE);
}
INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "e3"}),
- std::vector<std::string>({"otasigned_f4.zip", "f4"}),
- std::vector<std::string>({"otasigned_sha256.zip", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "ec", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v2"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v3"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v4"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v5"})));
INSTANTIATE_TEST_CASE_P(MultiKeySuccess, VerifierSuccessTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "f4", "e3"}),
- std::vector<std::string>({"otasigned_f4.zip", "ec", "f4"}),
- std::vector<std::string>({"otasigned_sha256.zip", "ec", "e3", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "ec", "sha256", "e3", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "f4", "sha256", "e3", "ec", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v1", "v2"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v5", "v2"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v5", "v1", "v3"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v5", "v1", "v4"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v4", "v1", "v5"})));
INSTANTIATE_TEST_CASE_P(WrongKey, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "f4"}),
- std::vector<std::string>({"otasigned_f4.zip", "e3"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip", "e3", "sha256"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v2"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v5"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v5"}),
+ std::vector<std::string>({"otasigned_v5.zip", "v3"})));
INSTANTIATE_TEST_CASE_P(WrongHash, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"otasigned.zip", "e3", "sha256"}),
- std::vector<std::string>({"otasigned_f4.zip", "f4", "sha256"}),
- std::vector<std::string>({"otasigned_sha256.zip"}),
- std::vector<std::string>({"otasigned_f4_sha256.zip", "f4"}),
- std::vector<std::string>({"otasigned_ecdsa_sha256.zip"})));
+ ::testing::Values(
+ std::vector<std::string>({"otasigned_v1.zip", "v3"}),
+ std::vector<std::string>({"otasigned_v2.zip", "v4"}),
+ std::vector<std::string>({"otasigned_v3.zip", "v1"}),
+ std::vector<std::string>({"otasigned_v4.zip", "v2"})));
INSTANTIATE_TEST_CASE_P(BadPackage, VerifierFailureTest,
- ::testing::Values(
- std::vector<std::string>({"random.zip"}),
- std::vector<std::string>({"fake-eocd.zip"}),
- std::vector<std::string>({"alter-metadata.zip"}),
- std::vector<std::string>({"alter-footer.zip"})));
+ ::testing::Values(
+ std::vector<std::string>({"random.zip", "v1"}),
+ std::vector<std::string>({"fake-eocd.zip", "v1"}),
+ std::vector<std::string>({"alter-metadata.zip", "v1"}),
+ std::vector<std::string>({"alter-footer.zip", "v1"})));