/* * 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 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 #include #include #include #include #include #include #include #include #include #include "applypatch/applypatch_modes.h" #include "common/test_constants.h" #include "otautil/paths.h" #include "otautil/print_sha1.h" #include "otautil/sysutil.h" using namespace std::string_literals; // Loads a given partition and returns a string of form "EMMC:name:size:hash". static std::string GetEmmcTargetString(const std::string& filename, const std::string& display_name = "") { std::string data; if (!android::base::ReadFileToString(filename, &data)) { PLOG(ERROR) << "Failed to read " << filename; return {}; } uint8_t digest[SHA_DIGEST_LENGTH]; SHA1(reinterpret_cast(data.c_str()), data.size(), digest); return "EMMC:"s + (display_name.empty() ? filename : display_name) + ":" + std::to_string(data.size()) + ":" + print_sha1(digest); } class ApplyPatchModesTest : public ::testing::Test { protected: void SetUp() override { source = GetEmmcTargetString(from_testdata_base("boot.img")); ASSERT_FALSE(source.empty()); std::string recovery_file = from_testdata_base("recovery.img"); recovery = GetEmmcTargetString(recovery_file); ASSERT_FALSE(recovery.empty()); ASSERT_TRUE(android::base::WriteStringToFile("", patched_file_.path)); target = GetEmmcTargetString(recovery_file, patched_file_.path); ASSERT_FALSE(target.empty()); Paths::Get().set_cache_temp_source(cache_source_.path); } std::string source; std::string target; std::string recovery; private: TemporaryFile cache_source_; TemporaryFile patched_file_; }; static int InvokeApplyPatchModes(const std::vector& args) { auto args_to_call = StringVectorToNullTerminatedArray(args); return applypatch_modes(args_to_call.size() - 1, args_to_call.data()); } static void VerifyPatchedTarget(const std::string& target) { std::vector pieces = android::base::Split(target, ":"); ASSERT_EQ(4, pieces.size()); ASSERT_EQ("EMMC", pieces[0]); std::string patched_emmc = GetEmmcTargetString(pieces[1]); ASSERT_FALSE(patched_emmc.empty()); ASSERT_EQ(target, patched_emmc); } TEST_F(ApplyPatchModesTest, InvalidArgs) { // At least two args (including the filename). ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch" })); // Unrecognized args. ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "-x" })); } TEST_F(ApplyPatchModesTest, PatchModeEmmcTarget) { std::vector args{ "applypatch", "--bonus", from_testdata_base("bonus.file"), "--patch", from_testdata_base("recovery-from-boot.p"), "--target", target, "--source", source, }; ASSERT_EQ(0, InvokeApplyPatchModes(args)); VerifyPatchedTarget(target); } // Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has // everything). TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithoutBonusFile) { std::vector args{ "applypatch", "--patch", from_testdata_base("recovery-from-boot-with-bonus.p"), "--target", target, "--source", source, }; ASSERT_EQ(0, InvokeApplyPatchModes(args)); VerifyPatchedTarget(target); } // Ensures that applypatch works with a bsdiff based recovery-from-boot.p. TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) { // Generate the bsdiff patch of recovery-from-boot.p. std::string src_content; ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("boot.img"), &src_content)); std::string tgt_content; ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("recovery.img"), &tgt_content)); TemporaryFile patch_file; ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast(src_content.data()), src_content.size(), reinterpret_cast(tgt_content.data()), tgt_content.size(), patch_file.path, nullptr)); std::vector args{ "applypatch", "--patch", patch_file.path, "--target", target, "--source", source, }; ASSERT_EQ(0, InvokeApplyPatchModes(args)); VerifyPatchedTarget(target); } TEST_F(ApplyPatchModesTest, PatchModeInvalidArgs) { // Invalid bonus file. std::vector args{ "applypatch", "--bonus", "/doesntexist", "--patch", from_testdata_base("recovery-from-boot.p"), "--target", target, "--source", source, }; ASSERT_NE(0, InvokeApplyPatchModes(args)); // With bonus file, but missing args. ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--bonus", from_testdata_base("bonus.file") })); } TEST_F(ApplyPatchModesTest, FlashMode) { std::vector args{ "applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target, }; ASSERT_EQ(0, InvokeApplyPatchModes(args)); VerifyPatchedTarget(target); } TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) { std::vector args{ "applypatch", "--bonus", from_testdata_base("bonus.file"), "--flash", source, "--target", target, }; ASSERT_NE(0, InvokeApplyPatchModes(args)); } TEST_F(ApplyPatchModesTest, CheckMode) { ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", recovery })); ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--check", source })); } TEST_F(ApplyPatchModesTest, CheckModeInvalidArgs) { ASSERT_EQ(2, InvokeApplyPatchModes({ "applypatch", "--check" })); } TEST_F(ApplyPatchModesTest, CheckModeNonEmmcTarget) { ASSERT_NE(0, InvokeApplyPatchModes({ "applypatch", "--check", from_testdata_base("boot.img") })); } TEST_F(ApplyPatchModesTest, ShowLicenses) { ASSERT_EQ(0, InvokeApplyPatchModes({ "applypatch", "--license" })); }