| /* |
| * 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 <stdio.h> |
| #include <stdlib.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/strings.h> |
| #include <bsdiff/bsdiff.h> |
| #include <gtest/gtest.h> |
| #include <openssl/sha.h> |
| |
| #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<const uint8_t*>(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<std::string>& 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<std::string> 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<std::string> 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<std::string> 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<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::vector<std::string> 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<std::string> 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<std::string> args{ |
| "applypatch", "--flash", from_testdata_base("recovery.img"), "--target", target, |
| }; |
| ASSERT_EQ(0, InvokeApplyPatchModes(args)); |
| VerifyPatchedTarget(target); |
| } |
| |
| TEST_F(ApplyPatchModesTest, FlashModeInvalidArgs) { |
| std::vector<std::string> 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" })); |
| } |