summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Android.bp190
-rw-r--r--tests/Android.mk223
-rw-r--r--tests/AndroidTest.xml31
-rw-r--r--tests/common/test_constants.h15
-rw-r--r--tests/component/applypatch_test.cpp387
-rw-r--r--tests/component/install_test.cpp387
-rw-r--r--tests/component/update_verifier_test.cpp95
-rw-r--r--tests/component/updater_test.cpp933
-rw-r--r--tests/component/verifier_test.cpp222
-rw-r--r--tests/manual/recovery_test.cpp181
-rw-r--r--tests/testdata/battery_scale.pngbin0 -> 463 bytes
-rw-r--r--tests/testdata/font.pngbin0 -> 24437 bytes
-rw-r--r--tests/testdata/gzipped_sourcebin0 -> 1436 bytes
-rw-r--r--tests/testdata/gzipped_targetbin0 -> 1502 bytes
-rw-r--r--tests/testdata/jarsigned.zipbin2271 -> 0 bytes
-rw-r--r--tests/testdata/loop00000.pngbin0 -> 5900 bytes
-rw-r--r--tests/testdata/new.filebin1388877 -> 0 bytes
-rw-r--r--tests/testdata/old.filebin1348051 -> 0 bytes
-rw-r--r--tests/testdata/otasigned_4096bits.zipbin0 -> 4055 bytes
-rw-r--r--tests/testdata/patch.bsdiffbin57476 -> 0 bytes
-rw-r--r--tests/testdata/testkey_4096bits.x509.pem35
-rw-r--r--tests/testdata/unsigned.zipbin376 -> 0 bytes
-rw-r--r--tests/unit/applypatch_modes_test.cpp198
-rw-r--r--tests/unit/applypatch_test.cpp290
-rw-r--r--tests/unit/asn1_decoder_test.cpp2
-rw-r--r--tests/unit/bootloader_message_test.cpp (renamed from tests/component/bootloader_message_test.cpp)2
-rw-r--r--tests/unit/commands_test.cpp554
-rw-r--r--tests/unit/dirutil_test.cpp5
-rw-r--r--tests/unit/edify_test.cpp (renamed from tests/component/edify_test.cpp)75
-rw-r--r--tests/unit/fuse_provider_test.cpp103
-rw-r--r--tests/unit/fuse_sideload_test.cpp (renamed from tests/component/sideload_test.cpp)40
-rw-r--r--tests/unit/imgdiff_test.cpp (renamed from tests/component/imgdiff_test.cpp)108
-rw-r--r--tests/unit/install_test.cpp597
-rw-r--r--tests/unit/locale_test.cpp4
-rw-r--r--tests/unit/minui_test.cpp54
-rw-r--r--tests/unit/package_test.cpp116
-rw-r--r--tests/unit/parse_install_logs_test.cpp74
-rw-r--r--tests/unit/rangeset_test.cpp27
-rw-r--r--tests/unit/resources_test.cpp136
-rw-r--r--tests/unit/screen_ui_test.cpp562
-rw-r--r--tests/unit/sysutil_test.cpp77
-rw-r--r--tests/unit/uncrypt_test.cpp (renamed from tests/component/uncrypt_test.cpp)1
-rw-r--r--tests/unit/update_verifier_test.cpp219
-rw-r--r--tests/unit/updater_test.cpp1219
-rw-r--r--tests/unit/verifier_test.cpp364
-rw-r--r--tests/unit/zip_test.cpp6
46 files changed, 4984 insertions, 2548 deletions
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 000000000..67a65ae9e
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,190 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "recovery_test_defaults",
+
+ defaults: [
+ "recovery_defaults",
+ ],
+
+ include_dirs: [
+ "bootable/recovery",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ "libcutils",
+ "liblog",
+ "libpng",
+ "libprocessgroup",
+ "libselinux",
+ "libz",
+ "libziparchive",
+ ],
+
+ target: {
+ android: {
+ shared_libs: [
+ "libutils",
+ ],
+ },
+
+ host: {
+ static_libs: [
+ "libutils",
+ ],
+ },
+ },
+}
+
+// libapplypatch, libapplypatch_modes, libimgdiff, libimgpatch
+libapplypatch_static_libs = [
+ "libapplypatch_modes",
+ "libapplypatch",
+ "libedify",
+ "libimgdiff",
+ "libimgpatch",
+ "libotautil",
+ "libbsdiff",
+ "libbspatch",
+ "libdivsufsort",
+ "libdivsufsort64",
+ "libutils",
+ "libbase",
+ "libbrotli",
+ "libbz",
+ "libcrypto",
+ "libz",
+ "libziparchive",
+]
+
+// librecovery_defaults uses many shared libs that we want to avoid using in tests (e.g. we don't
+// have 32-bit android.hardware.health@2.0.so or libbootloader_message.so on marlin).
+librecovery_static_libs = [
+ "librecovery",
+ "librecovery_fastboot",
+ "libinstall",
+ "librecovery_ui",
+ "libminui",
+ "libfusesideload",
+ "libbootloader_message",
+ "libotautil",
+
+ "libhealthhalutils",
+ "libvintf_recovery",
+ "libvintf",
+
+ "android.hardware.health@2.0",
+ "android.hardware.health@1.0",
+ "libext4_utils",
+ "libfs_mgr",
+ "libhidl-gen-utils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder_noltopgo",
+ "libbinderthreadstate",
+ "liblp",
+ "libvndksupport",
+ "libtinyxml2",
+]
+
+cc_test {
+ name: "recovery_unit_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ "libupdater_defaults",
+ ],
+
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "unit/*.cpp",
+ ],
+
+ static_libs: libapplypatch_static_libs + librecovery_static_libs + [
+ "librecovery_ui",
+ "libfusesideload",
+ "libminui",
+ "libotautil",
+ "libupdater",
+ "libupdate_verifier",
+
+ "libgtest_prod",
+ "libprotobuf-cpp-lite",
+ ],
+
+ data: [
+ "testdata/*",
+ ":res-testdata",
+ ],
+}
+
+cc_test {
+ name: "recovery_manual_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ ],
+
+ test_suites: ["device-tests"],
+
+ srcs: [
+ "manual/recovery_test.cpp",
+ ],
+}
+
+cc_test_host {
+ name: "recovery_host_test",
+ isolated: true,
+
+ defaults: [
+ "recovery_test_defaults",
+ ],
+
+ srcs: [
+ "unit/imgdiff_test.cpp",
+ ],
+
+ static_libs: [
+ "libimgdiff",
+ "libimgpatch",
+ "libotautil",
+ "libbsdiff",
+ "libbspatch",
+ "libziparchive",
+ "libutils",
+ "libcrypto",
+ "libbrotli",
+ "libbz",
+ "libdivsufsort64",
+ "libdivsufsort",
+ "libz",
+ ],
+
+ test_suites: ["general-tests"],
+
+ data: ["testdata/*"],
+
+ target: {
+ darwin: {
+ // libimgdiff is not available on the Mac.
+ enabled: false,
+ },
+ },
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index b3584fe87..000000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,223 +0,0 @@
-#
-# Copyright (C) 2014 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.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-# Unit tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := recovery_unit_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_STATIC_LIBRARIES := \
- libverifier \
- libminui \
- libotautil \
- libupdater \
- libziparchive \
- libutils \
- libz \
- libselinux \
- libbase \
- libBionicGtestMain
-
-LOCAL_SRC_FILES := \
- unit/asn1_decoder_test.cpp \
- unit/dirutil_test.cpp \
- unit/locale_test.cpp \
- unit/rangeset_test.cpp \
- unit/sysutil_test.cpp \
- unit/zip_test.cpp \
-
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SHARED_LIBRARIES := liblog
-include $(BUILD_NATIVE_TEST)
-
-# Manual tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := recovery_manual_test
-LOCAL_STATIC_LIBRARIES := \
- libminui \
- libbase \
- libBionicGtestMain
-
-LOCAL_SRC_FILES := manual/recovery_test.cpp
-LOCAL_SHARED_LIBRARIES := \
- liblog \
- libpng
-
-resource_files := $(call find-files-in-subdirs, bootable/recovery, \
- "*_text.png", \
- res-mdpi/images \
- res-hdpi/images \
- res-xhdpi/images \
- res-xxhdpi/images \
- res-xxxhdpi/images \
- )
-
-# The resource image files that will go to $OUT/data/nativetest/recovery.
-testimage_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
-GEN := $(addprefix $(testimage_out_path)/, $(resource_files))
-
-$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
-$(GEN): $(testimage_out_path)/% : bootable/recovery/%
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN)
-
-include $(BUILD_NATIVE_TEST)
-
-# Component tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := \
- -Wall \
- -Werror \
- -D_FILE_OFFSET_BITS=64
-
-ifeq ($(AB_OTA_UPDATER),true)
-LOCAL_CFLAGS += -DAB_OTA_UPDATER=1
-endif
-
-ifeq ($(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VERITY),true)
-LOCAL_CFLAGS += -DPRODUCT_SUPPORTS_VERITY=1
-endif
-
-ifeq ($(BOARD_AVB_ENABLE),true)
-LOCAL_CFLAGS += -DBOARD_AVB_ENABLE=1
-endif
-
-LOCAL_MODULE := recovery_component_test
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SRC_FILES := \
- component/applypatch_test.cpp \
- component/bootloader_message_test.cpp \
- component/edify_test.cpp \
- component/imgdiff_test.cpp \
- component/install_test.cpp \
- component/sideload_test.cpp \
- component/uncrypt_test.cpp \
- component/updater_test.cpp \
- component/update_verifier_test.cpp \
- component/verifier_test.cpp
-
-LOCAL_SHARED_LIBRARIES := \
- libhidlbase
-
-tune2fs_static_libraries := \
- libext2_com_err \
- libext2_blkid \
- libext2_quota \
- libext2_uuid \
- libext2_e2p \
- libext2fs
-
-LOCAL_STATIC_LIBRARIES := \
- libapplypatch_modes \
- libapplypatch \
- libedify \
- libimgdiff \
- libimgpatch \
- libbsdiff \
- libbspatch \
- libfusesideload \
- libotafault \
- librecovery \
- libupdater \
- libbootloader_message \
- libverifier \
- libotautil \
- libmounts \
- libupdate_verifier \
- libdivsufsort \
- libdivsufsort64 \
- libfs_mgr \
- libvintf_recovery \
- libvintf \
- libhidl-gen-utils \
- libtinyxml2 \
- libselinux \
- libext4_utils \
- libsparse \
- libcrypto_utils \
- libcrypto \
- libbz \
- libziparchive \
- liblog \
- libutils \
- libz \
- libbase \
- libtune2fs \
- libfec \
- libfec_rs \
- libsquashfs_utils \
- libcutils \
- libbrotli \
- libBionicGtestMain \
- $(tune2fs_static_libraries)
-
-testdata_files := $(call find-subdir-files, testdata/*)
-
-# The testdata files that will go to $OUT/data/nativetest/recovery.
-testdata_out_path := $(TARGET_OUT_DATA)/nativetest/recovery
-GEN := $(addprefix $(testdata_out_path)/, $(testdata_files))
-$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
-$(GEN): $(testdata_out_path)/% : $(LOCAL_PATH)/%
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN)
-
-# A copy of the testdata to be packed into continuous_native_tests.zip.
-testdata_continuous_zip_prefix := \
- $(call intermediates-dir-for,PACKAGING,recovery_component_test)/DATA
-testdata_continuous_zip_path := $(testdata_continuous_zip_prefix)/nativetest/recovery
-GEN := $(addprefix $(testdata_continuous_zip_path)/, $(testdata_files))
-$(GEN): PRIVATE_PATH := $(LOCAL_PATH)
-$(GEN): PRIVATE_CUSTOM_TOOL = cp $< $@
-$(GEN): $(testdata_continuous_zip_path)/% : $(LOCAL_PATH)/%
- $(transform-generated-source)
-LOCAL_GENERATED_SOURCES += $(GEN)
-LOCAL_PICKUP_FILES := $(testdata_continuous_zip_prefix)
-
-include $(BUILD_NATIVE_TEST)
-
-# Host tests
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_MODULE := recovery_host_test
-LOCAL_MODULE_HOST_OS := linux
-LOCAL_C_INCLUDES := bootable/recovery
-LOCAL_SRC_FILES := \
- component/imgdiff_test.cpp
-LOCAL_STATIC_LIBRARIES := \
- libimgdiff \
- libimgpatch \
- libotautil \
- libbsdiff \
- libbspatch \
- libziparchive \
- libutils \
- libbase \
- libcrypto \
- libbrotli \
- libbz \
- libdivsufsort64 \
- libdivsufsort \
- libz \
- libBionicGtestMain
-LOCAL_SHARED_LIBRARIES := \
- liblog
-include $(BUILD_HOST_NATIVE_TEST)
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
deleted file mode 100644
index 3999aa57d..000000000
--- a/tests/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for recovery_component_test and recovery_unit_test">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="recovery_component_test->/data/local/tmp/recovery_component_test" />
- <option name="push" value="recovery_unit_test->/data/local/tmp/recovery_unit_test" />
- </target_preparer>
- <option name="test-suite-tag" value="apct" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="recovery_component_test" />
- </test>
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="recovery_unit_test" />
- </test>
-</configuration>
diff --git a/tests/common/test_constants.h b/tests/common/test_constants.h
index 514818e0a..b6c27a754 100644
--- a/tests/common/test_constants.h
+++ b/tests/common/test_constants.h
@@ -17,10 +17,10 @@
#ifndef _OTA_TEST_CONSTANTS_H
#define _OTA_TEST_CONSTANTS_H
-#include <stdlib.h>
-
#include <string>
+#include <android-base/file.h>
+
// Zip entries in ziptest_valid.zip.
static const std::string kATxtContents("abcdefghabcdefgh\n");
static const std::string kBTxtContents("abcdefgh\n");
@@ -32,14 +32,9 @@ static const std::string kATxtSha1Sum("32c96a03dc8cd20097940f351bca6261ee5a1643"
// echo -n -e "abcdefgh\n" | sha1sum
static const std::string kBTxtSha1Sum("e414af7161c9554089f4106d6f1797ef14a73666");
-static std::string from_testdata_base(const std::string& fname) {
-#ifdef __ANDROID__
- static std::string data_root = getenv("ANDROID_DATA");
-#else
- static std::string data_root = std::string(getenv("ANDROID_PRODUCT_OUT")) + "/data";
-#endif
-
- return data_root + "/nativetest/recovery/testdata/" + fname;
+[[maybe_unused]] static std::string from_testdata_base(const std::string& fname) {
+ static std::string exec_dir = android::base::GetExecutableDirectory();
+ return exec_dir + "/testdata/" + fname;
}
#endif // _OTA_TEST_CONSTANTS_H
diff --git a/tests/component/applypatch_test.cpp b/tests/component/applypatch_test.cpp
deleted file mode 100644
index 61e06adb6..000000000
--- a/tests/component/applypatch_test.cpp
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * 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 <fcntl.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/statvfs.h>
-#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 <bsdiff/bsdiff.h>
-#include <openssl/sha.h>
-
-#include "applypatch/applypatch.h"
-#include "applypatch/applypatch_modes.h"
-#include "common/test_constants.h"
-#include "otautil/cache_location.h"
-#include "otautil/print_sha1.h"
-
-using namespace std::string_literals;
-
-static void sha1sum(const std::string& fname, std::string* sha1, size_t* fsize = nullptr) {
- ASSERT_NE(nullptr, sha1);
-
- std::string data;
- ASSERT_TRUE(android::base::ReadFileToString(fname, &data));
-
- if (fsize != nullptr) {
- *fsize = data.size();
- }
-
- uint8_t digest[SHA_DIGEST_LENGTH];
- SHA1(reinterpret_cast<const uint8_t*>(data.c_str()), data.size(), digest);
- *sha1 = print_sha1(digest);
-}
-
-static void mangle_file(const std::string& fname) {
- std::string content(1024, '\0');
- for (size_t i = 0; i < 1024; i++) {
- content[i] = rand() % 256;
- }
- ASSERT_TRUE(android::base::WriteStringToFile(content, fname));
-}
-
-class ApplyPatchTest : public ::testing::Test {
- public:
- virtual void SetUp() override {
- // set up files
- old_file = from_testdata_base("old.file");
- new_file = from_testdata_base("new.file");
- nonexistent_file = from_testdata_base("nonexistent.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());
- }
-
- std::string old_file;
- std::string new_file;
- std::string nonexistent_file;
-
- std::string old_sha1;
- std::string new_sha1;
- std::string bad_sha1_a;
- std::string bad_sha1_b;
-
- size_t old_size;
- size_t new_size;
-};
-
-class ApplyPatchCacheTest : public ApplyPatchTest {
- protected:
- void SetUp() override {
- ApplyPatchTest::SetUp();
- CacheLocation::location().set_cache_temp_source(old_file);
- }
-};
-
-class ApplyPatchModesTest : public ::testing::Test {
- protected:
- void SetUp() override {
- CacheLocation::location().set_cache_temp_source(cache_source.path);
- }
-
- TemporaryFile cache_source;
-};
-
-TEST_F(ApplyPatchTest, CheckModeSkip) {
- std::vector<std::string> sha1s;
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchTest, CheckModeSingle) {
- std::vector<std::string> sha1s = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(&old_file[0], sha1s));
-}
-
-TEST_F(ApplyPatchTest, CheckModeMultiple) {
- 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) {
- 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, CheckCacheCorruptedSourceSingle) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_single = { old_sha1 };
- ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_single));
- ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_single));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceMultiple) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_multiple = { bad_sha1_a, old_sha1, bad_sha1_b };
- ASSERT_EQ(0, applypatch_check(temp_file.path, sha1s_multiple));
- ASSERT_EQ(0, applypatch_check(nonexistent_file.c_str(), sha1s_multiple));
-}
-
-TEST_F(ApplyPatchCacheTest, CheckCacheCorruptedSourceFailure) {
- TemporaryFile temp_file;
- mangle_file(temp_file.path);
- std::vector<std::string> sha1s_failure = { bad_sha1_a, bad_sha1_b };
- ASSERT_NE(0, applypatch_check(temp_file.path, sha1s_failure));
- ASSERT_NE(0, applypatch_check(nonexistent_file.c_str(), sha1s_failure));
-}
-
-TEST_F(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(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()));
-}
-
-// Ensures that applypatch works with a bsdiff based recovery-from-boot.p.
-TEST_F(ApplyPatchModesTest, PatchModeEmmcTargetWithBsdiffPatch) {
- std::string boot_img_file = from_testdata_base("boot.img");
- std::string boot_img_sha1;
- size_t boot_img_size;
- sha1sum(boot_img_file, &boot_img_sha1, &boot_img_size);
-
- std::string recovery_img_file = from_testdata_base("recovery.img");
- std::string recovery_img_sha1;
- size_t recovery_img_size;
- sha1sum(recovery_img_file, &recovery_img_sha1, &recovery_img_size);
-
- // Generate the bsdiff patch of recovery-from-boot.p.
- std::string src_content;
- ASSERT_TRUE(android::base::ReadFileToString(boot_img_file, &src_content));
-
- std::string tgt_content;
- ASSERT_TRUE(android::base::ReadFileToString(recovery_img_file, &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));
-
- // applypatch <src-file> <tgt-file> <tgt-sha1> <tgt-size> <src-sha1>:<patch>
- std::string src_file_arg =
- "EMMC:" + boot_img_file + ":" + std::to_string(boot_img_size) + ":" + boot_img_sha1;
- TemporaryFile tgt_file;
- std::string tgt_file_arg = "EMMC:"s + tgt_file.path;
- std::string recovery_img_size_arg = std::to_string(recovery_img_size);
- std::string patch_arg = boot_img_sha1 + ":" + patch_file.path;
- std::vector<const char*> args = { "applypatch",
- src_file_arg.c_str(),
- tgt_file_arg.c_str(),
- recovery_img_sha1.c_str(),
- recovery_img_size_arg.c_str(),
- patch_arg.c_str() };
- ASSERT_EQ(0, applypatch_modes(args.size(), args.data()));
-
- // Double check the patched recovery image.
- std::string tgt_file_sha1;
- size_t tgt_file_size;
- sha1sum(tgt_file.path, &tgt_file_sha1, &tgt_file_size);
- ASSERT_EQ(recovery_img_size, tgt_file_size);
- ASSERT_EQ(recovery_img_sha1, tgt_file_sha1);
-}
-
-TEST_F(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(ApplyPatchModesTest, CheckModeInvalidArgs) {
- // Insufficient args.
- ASSERT_EQ(2, applypatch_modes(2, (const char* []){ "applypatch", "-c" }));
-}
-
-TEST_F(ApplyPatchModesTest, ShowLicenses) {
- ASSERT_EQ(0, applypatch_modes(2, (const char* []){ "applypatch", "-l" }));
-}
diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp
deleted file mode 100644
index d19d788e4..000000000
--- a/tests/component/install_test.cpp
+++ /dev/null
@@ -1,387 +0,0 @@
-/*
- * 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 <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#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.release(), "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.release(), "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, read_metadata_from_package_smoke) {
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("META-INF/com/android/metadata", kCompressStored));
- const std::string content("abcdefg");
- ASSERT_EQ(0, writer.WriteBytes(content.data(), 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::string metadata;
- ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
- ASSERT_EQ(content, metadata);
- CloseArchive(zip);
-
- TemporaryFile temp_file2;
- FILE* zip_file2 = fdopen(temp_file2.release(), "w");
- ZipWriter writer2(zip_file2);
- ASSERT_EQ(0, writer2.StartEntry("META-INF/com/android/metadata", kCompressDeflated));
- ASSERT_EQ(0, writer2.WriteBytes(content.data(), content.size()));
- ASSERT_EQ(0, writer2.FinishEntry());
- ASSERT_EQ(0, writer2.Finish());
- ASSERT_EQ(0, fclose(zip_file2));
-
- ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
- metadata.clear();
- ASSERT_TRUE(read_metadata_from_package(zip, &metadata));
- ASSERT_EQ(content, metadata);
- CloseArchive(zip);
-}
-
-TEST(InstallTest, read_metadata_from_package_no_entry) {
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- ASSERT_EQ(0, writer.StartEntry("dummy_entry", kCompressStored));
- 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::string metadata;
- ASSERT_FALSE(read_metadata_from_package(zip, &metadata));
- CloseArchive(zip);
-}
-
-TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
- TemporaryFile compatibility_zip_file;
- FILE* compatibility_zip = fdopen(compatibility_zip_file.release(), "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.release(), "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.release(), "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.release(), "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);
-}
-
-#ifdef AB_OTA_UPDATER
-static void VerifyAbUpdateBinaryCommand(const std::string& serialno, bool success = true) {
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "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::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
- "post-timestamp=" + timestamp };
- if (!serialno.empty()) {
- meta.push_back("serialno=" + serialno);
- }
- std::string metadata = android::base::Join(meta, "\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));
- ZipString payload_name("payload.bin");
- ZipEntry payload_entry;
- ASSERT_EQ(0, FindEntry(zip, payload_name, &payload_entry));
- int status_fd = 10;
- std::string package = "/path/to/update.zip";
- std::string binary_path = "/sbin/update_engine_sideload";
- std::vector<std::string> cmd;
- if (success) {
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("--payload=file://" + package, cmd[1]);
- ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
- ASSERT_EQ("--headers=" + properties, cmd[3]);
- ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
- } else {
- ASSERT_EQ(INSTALL_ERROR, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- }
- CloseArchive(zip);
-}
-#endif // AB_OTA_UPDATER
-
-TEST(InstallTest, update_binary_command_smoke) {
-#ifdef AB_OTA_UPDATER
- // Empty serialno will pass the verification.
- VerifyAbUpdateBinaryCommand({});
-#else
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "w");
- ZipWriter writer(zip_file);
- static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
- ASSERT_EQ(0, writer.StartEntry(UPDATE_BINARY_NAME, kCompressStored));
- 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 package = "/path/to/update.zip";
- TemporaryDir td;
- std::string binary_path = std::string(td.path) + "/update_binary";
- std::vector<std::string> cmd;
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- ASSERT_EQ(4U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
- ASSERT_EQ(std::to_string(status_fd), cmd[2]);
- ASSERT_EQ(package, cmd[3]);
- struct stat sb;
- ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
- ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-
- // With non-zero retry count. update_binary will be removed automatically.
- cmd.clear();
- ASSERT_EQ(0, update_binary_command(package, zip, binary_path, 2, status_fd, &cmd));
- ASSERT_EQ(5U, cmd.size());
- ASSERT_EQ(binary_path, cmd[0]);
- ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
- ASSERT_EQ(std::to_string(status_fd), cmd[2]);
- ASSERT_EQ(package, cmd[3]);
- ASSERT_EQ("retry", cmd[4]);
- sb = {};
- ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
- ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
-
- CloseArchive(zip);
-#endif // AB_OTA_UPDATER
-}
-
-TEST(InstallTest, update_binary_command_invalid) {
-#ifdef AB_OTA_UPDATER
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "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 package = "/path/to/update.zip";
- std::string binary_path = "/sbin/update_engine_sideload";
- std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- CloseArchive(zip);
-#else
- TemporaryFile temp_file;
- FILE* zip_file = fdopen(temp_file.release(), "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));
-
- // Missing update binary.
- ZipArchiveHandle zip;
- ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
- int status_fd = 10;
- std::string package = "/path/to/update.zip";
- TemporaryDir td;
- std::string binary_path = std::string(td.path) + "/update_binary";
- std::vector<std::string> cmd;
- ASSERT_EQ(INSTALL_CORRUPT, update_binary_command(package, zip, binary_path, 0, status_fd, &cmd));
- CloseArchive(zip);
-#endif // AB_OTA_UPDATER
-}
-
-#ifdef AB_OTA_UPDATER
-TEST(InstallTest, update_binary_command_multiple_serialno) {
- std::string serialno = android::base::GetProperty("ro.serialno", "");
- ASSERT_NE("", serialno);
-
- // Single matching serialno will pass the verification.
- VerifyAbUpdateBinaryCommand(serialno);
-
- static constexpr char alphabet[] =
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
- auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
-
- // Generate 900 random serial numbers.
- std::string random_serial;
- for (size_t i = 0; i < 900; i++) {
- generate_n(back_inserter(random_serial), serialno.size(), generator);
- random_serial.append("|");
- }
- // Random serialnos should fail the verification.
- VerifyAbUpdateBinaryCommand(random_serial, false);
-
- std::string long_serial = random_serial + serialno + "|";
- for (size_t i = 0; i < 99; i++) {
- generate_n(back_inserter(long_serial), serialno.size(), generator);
- long_serial.append("|");
- }
- // String with the matching serialno should pass the verification.
- VerifyAbUpdateBinaryCommand(long_serial);
-}
-#endif // AB_OTA_UPDATER
diff --git a/tests/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp
deleted file mode 100644
index 1544bb2a4..000000000
--- a/tests/component/update_verifier_test.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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 <string>
-
-#include <android-base/file.h>
-#include <android-base/test_utils.h>
-#include <gtest/gtest.h>
-#include <update_verifier/update_verifier.h>
-
-class UpdateVerifierTest : public ::testing::Test {
- protected:
- void SetUp() override {
-#if defined(PRODUCT_SUPPORTS_VERITY) || defined(BOARD_AVB_ENABLE)
- verity_supported = true;
-#else
- verity_supported = false;
-#endif
- }
-
- bool verity_supported;
-};
-
-TEST_F(UpdateVerifierTest, verify_image_no_care_map) {
- // Non-existing care_map is allowed.
- ASSERT_TRUE(verify_image("/doesntexist"));
-}
-
-TEST_F(UpdateVerifierTest, verify_image_smoke) {
- // This test relies on dm-verity support.
- if (!verity_supported) {
- GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
- return;
- }
-
- TemporaryFile temp_file;
- std::string content = "system\n2,0,1";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
-
- // Leading and trailing newlines should be accepted.
- ASSERT_TRUE(android::base::WriteStringToFile("\n" + content + "\n\n", temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
-}
-
-TEST_F(UpdateVerifierTest, verify_image_wrong_lines) {
- // The care map file can have only 2 / 4 / 6 lines.
- TemporaryFile temp_file;
- ASSERT_FALSE(verify_image(temp_file.path));
-
- ASSERT_TRUE(android::base::WriteStringToFile("line1", temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
-
- ASSERT_TRUE(android::base::WriteStringToFile("line1\nline2\nline3", temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
-}
-
-TEST_F(UpdateVerifierTest, verify_image_malformed_care_map) {
- // This test relies on dm-verity support.
- if (!verity_supported) {
- GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
- return;
- }
-
- TemporaryFile temp_file;
- std::string content = "system\n2,1,0";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_FALSE(verify_image(temp_file.path));
-}
-
-TEST_F(UpdateVerifierTest, verify_image_legacy_care_map) {
- // This test relies on dm-verity support.
- if (!verity_supported) {
- GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
- return;
- }
-
- TemporaryFile temp_file;
- std::string content = "/dev/block/bootdevice/by-name/system\n2,1,0";
- ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
- ASSERT_TRUE(verify_image(temp_file.path));
-}
diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp
deleted file mode 100644
index 5bfd7cb40..000000000
--- a/tests/component/updater_test.cpp
+++ /dev/null
@@ -1,933 +0,0 @@
-/*
- * 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 <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#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 <brotli/encode.h>
-#include <bsdiff/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 "otautil/SysUtil.h"
-#include "otautil/cache_location.h"
-#include "otautil/error_code.h"
-#include "otautil/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 void BuildUpdatePackage(const std::unordered_map<std::string, std::string>& entries,
- int fd) {
- FILE* zip_file_ptr = fdopen(fd, "wb");
- ZipWriter zip_writer(zip_file_ptr);
-
- for (const auto& entry : entries) {
- ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
- if (!entry.second.empty()) {
- ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
- }
- ASSERT_EQ(0, zip_writer.FinishEntry());
- }
-
- ASSERT_EQ(0, zip_writer.Finish());
- ASSERT_EQ(0, fclose(zip_file_ptr));
-}
-
-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();
-
- // Mock the location of last_command_file.
- CacheLocation::location().set_cache_temp_source(temp_saved_source_.path);
- CacheLocation::location().set_last_command_file(temp_last_command_.path);
- CacheLocation::location().set_stash_directory_base(temp_stash_base_.path);
- }
-
- TemporaryFile temp_saved_source_;
- TemporaryFile temp_last_command_;
- TemporaryDir temp_stash_base_;
-};
-
-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);
-}
-
-// 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.release(), "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());
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
-}
-
-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.release(), "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());
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
-}
-
-TEST_F(UpdaterTest, block_image_update_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');
-
- // Generate the patch data.
- 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));
-
- // Create the transfer list that contains a bsdiff.
- 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,
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", "" },
- { "patch_data", patch_content },
- { "transfer_list", android::base::Join(transfer_list, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- // Execute the commands in the 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));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, block_image_update_fail) {
- std::string src_content(4096 * 2, 'e');
- std::string src_hash = get_sha1(src_content);
- // Stash and free some blocks, then fail the update intentionally.
- std::vector<std::string> transfer_list = {
- "4", "2", "0", "2", "stash " + src_hash + " 2,0,2", "free " + src_hash, "fail",
- };
-
- // Add a new data of 10 bytes to test the deadlock.
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", std::string(10, 0) },
- { "patch_data", "" },
- { "transfer_list", android::base::Join(transfer_list, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- TemporaryFile update_file;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- // Expect the stashed blocks to be freed.
- std::string script = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("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 = std::string(temp_stash_base_.path) + "/" + name_digest;
- ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
- ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
- ASSERT_EQ(0, rmdir(stash_base.c_str()));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, new_data_over_write) {
- std::vector<std::string> transfer_list = {
- "4", "1", "0", "0", "new 2,0,1",
- };
-
- // Write 4096 + 100 bytes of new data.
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", std::string(4196, 0) },
- { "patch_data", "" },
- { "transfer_list", android::base::Join(transfer_list, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- TemporaryFile update_file;
- 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);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, new_data_short_write) {
- std::vector<std::string> transfer_list = {
- "4",
- "1",
- "0",
- "0",
- "new 2,0,1",
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "empty_new_data", "" },
- { "short_new_data", std::string(10, 'a') },
- { "exact_new_data", std::string(4096, 'a') },
- { "patch_data", "" },
- { "transfer_list", android::base::Join(transfer_list, '\n') },
- };
-
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- // Updater should report the failure gracefully rather than stuck in deadlock.
- TemporaryFile update_file;
- std::string script_empty_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "empty_new_data", "patch_data"))";
- expect("", script_empty_data.c_str(), kNoCause, &updater_info);
-
- std::string script_short_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "short_new_data", "patch_data"))";
- expect("", script_short_data.c_str(), kNoCause, &updater_info);
-
- // Expect to write 1 block of new data successfully.
- std::string script_exact_data = "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "exact_new_data", "patch_data"))";
- expect("t", script_exact_data.c_str(), kNoCause, &updater_info);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, brotli_new_data) {
- auto generator = []() { return rand() % 128; };
- // Generate 100 blocks of random data.
- std::string brotli_new_data;
- brotli_new_data.reserve(4096 * 100);
- generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
-
- size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
- std::string encoded_data(encoded_size, 0);
- ASSERT_TRUE(BrotliEncoderCompress(
- BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
- reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
- reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
- encoded_data.resize(encoded_size);
-
- // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
- // This helps us to catch potential short writes.
- std::vector<std::string> transfer_list = {
- "4",
- "100",
- "0",
- "0",
- "new 2,0,1",
- "new 2,1,2",
- "new 4,2,50,50,97",
- "new 2,97,98",
- "new 2,98,99",
- "new 2,99,100",
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "new.dat.br", std::move(encoded_data) },
- { "patch_data", "" },
- { "transfer_list", android::base::Join(transfer_list, '\n') },
- };
-
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wb");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- // Check if we can decompress the new data correctly.
- TemporaryFile update_file;
- std::string script_new_data =
- "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list"), "new.dat.br", "patch_data"))";
- expect("t", script_new_data.c_str(), kNoCause, &updater_info);
-
- std::string updated_content;
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_content));
- ASSERT_EQ(brotli_new_data, updated_content);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, last_command_update) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
- std::string block3 = std::string(4096, '3');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
- std::string block3_hash = get_sha1(block3);
-
- // Compose the transfer list to fail the first update.
- std::vector<std::string> transfer_list_fail = {
- "4",
- "2",
- "0",
- "2",
- "stash " + block1_hash + " 2,0,1",
- "move " + block1_hash + " 2,1,2 1 2,0,1",
- "stash " + block3_hash + " 2,2,3",
- "fail",
- };
-
- // Mimic a resumed update with the same transfer commands.
- std::vector<std::string> transfer_list_continue = {
- "4",
- "2",
- "0",
- "2",
- "stash " + block1_hash + " 2,0,1",
- "move " + block1_hash + " 2,1,2 1 2,0,1",
- "stash " + block3_hash + " 2,2,3",
- "move " + block1_hash + " 2,2,3 1 2,0,1",
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", "" },
- { "patch_data", "" },
- { "transfer_list_fail", android::base::Join(transfer_list_fail, '\n') },
- { "transfer_list_continue", android::base::Join(transfer_list_continue, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- std::string src_content = block1 + block2 + block3;
- 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_fail"), "new_data", "patch_data"))";
- expect("", script.c_str(), kNoCause, &updater_info);
-
- // Expect last_command to contain the last stash command.
- std::string last_command_content;
- ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
- EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
- std::string updated_contents;
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
- ASSERT_EQ(block1 + block1 + block3, updated_contents);
-
- // Resume the update, expect the first 'move' to be skipped but the second 'move' to be executed.
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- std::string script_second_update =
- "block_image_update(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list_continue"), "new_data", "patch_data"))";
- expect("t", script_second_update.c_str(), kNoCause, &updater_info);
- ASSERT_TRUE(android::base::ReadFileToString(update_file.path, &updated_contents));
- ASSERT_EQ(block1 + block2 + block1, updated_contents);
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, last_command_update_unresumable) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
-
- // Construct an unresumable update with source blocks mismatch.
- std::vector<std::string> transfer_list_unresumable = {
- "4", "2", "0", "2", "stash " + block1_hash + " 2,0,1", "move " + block2_hash + " 2,1,2 1 2,0,1",
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", "" },
- { "patch_data", "" },
- { "transfer_list_unresumable", android::base::Join(transfer_list_unresumable, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- // Set up the last_command_file
- ASSERT_TRUE(
- android::base::WriteStringToFile("0\nstash " + block1_hash + " 2,0,1", last_command_file));
-
- // The last_command_file will be deleted if the update encounters an unresumable failure
- // later.
- std::string src_content = block1 + block1;
- 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_unresumable"), "new_data", "patch_data"))";
- expect("", script.c_str(), kNoCause, &updater_info);
- ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
-
-TEST_F(UpdaterTest, last_command_verify) {
- std::string last_command_file = CacheLocation::location().last_command_file();
-
- std::string block1 = std::string(4096, '1');
- std::string block2 = std::string(4096, '2');
- std::string block3 = std::string(4096, '3');
- std::string block1_hash = get_sha1(block1);
- std::string block2_hash = get_sha1(block2);
- std::string block3_hash = get_sha1(block3);
-
- std::vector<std::string> transfer_list_verify = {
- "4",
- "2",
- "0",
- "2",
- "stash " + block1_hash + " 2,0,1",
- "move " + block1_hash + " 2,0,1 1 2,0,1",
- "move " + block1_hash + " 2,1,2 1 2,0,1",
- "stash " + block3_hash + " 2,2,3",
- };
-
- std::unordered_map<std::string, std::string> entries = {
- { "new_data", "" },
- { "patch_data", "" },
- { "transfer_list_verify", android::base::Join(transfer_list_verify, '\n') },
- };
-
- // Build the update package.
- TemporaryFile zip_file;
- BuildUpdatePackage(entries, zip_file.release());
-
- MemMapping map;
- ASSERT_TRUE(map.MapFile(zip_file.path));
- 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 = fdopen(temp_pipe.release(), "wbe");
- updater_info.package_zip_addr = map.addr;
- updater_info.package_zip_len = map.length;
-
- std::string src_content = block1 + block1 + block3;
- TemporaryFile update_file;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
-
- ASSERT_TRUE(
- android::base::WriteStringToFile("2\nstash " + block3_hash + " 2,2,3", last_command_file));
-
- // Expect the verification to succeed and the last_command_file is intact.
- std::string script_verify =
- "block_image_verify(\"" + std::string(update_file.path) +
- R"(", package_extract_file("transfer_list_verify"), "new_data","patch_data"))";
- expect("t", script_verify.c_str(), kNoCause, &updater_info);
-
- std::string last_command_content;
- ASSERT_TRUE(android::base::ReadFileToString(last_command_file.c_str(), &last_command_content));
- EXPECT_EQ("2\nstash " + block3_hash + " 2,2,3", last_command_content);
-
- // Expect the verification to succeed but last_command_file to be deleted; because the target
- // blocks don't have the expected contents for the second move command.
- src_content = block1 + block2 + block3;
- ASSERT_TRUE(android::base::WriteStringToFile(src_content, update_file.path));
- expect("t", script_verify.c_str(), kNoCause, &updater_info);
- ASSERT_EQ(-1, access(last_command_file.c_str(), R_OK));
-
- ASSERT_EQ(0, fclose(updater_info.cmd_pipe));
- CloseArchive(handle);
-}
diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp
deleted file mode 100644
index 2ef3828ad..000000000
--- a/tests/component/verifier_test.cpp
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * 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 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 <errno.h>
-#include <fcntl.h>
-#include <gtest/gtest.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <string>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-#include <android-base/test_utils.h>
-
-#include "common/test_constants.h"
-#include "otautil/SysUtil.h"
-#include "verifier.h"
-
-using namespace std::string_literals;
-
-class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
- protected:
- void SetUp() override {
- std::vector<std::string> args = GetParam();
- std::string package = from_testdata_base(args[0]);
- if (!memmap.MapFile(package)) {
- FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n";
- }
-
- 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 {
-};
-
-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_AlteredFooter) {
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
- TemporaryFile key_file1;
- ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
- std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file1.path, certs));
-
- std::string package;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
- ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6));
-
- // Alter the footer.
- package[package.size() - 5] = '\x05';
- ASSERT_EQ(VERIFY_FAILURE,
- verify_file(reinterpret_cast<const unsigned char*>(package.data()), package.size(),
- certs));
-}
-
-TEST(VerifierTest, BadPackage_AlteredContent) {
- std::string testkey_v3;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v3.txt"), &testkey_v3));
- TemporaryFile key_file1;
- ASSERT_TRUE(android::base::WriteStringToFile(testkey_v3, key_file1.path));
- std::vector<Certificate> certs;
- ASSERT_TRUE(load_keys(key_file1.path, certs));
-
- std::string package;
- ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
- ASSERT_GT(package.size(), static_cast<size_t>(100));
-
- // Alter the content.
- std::string altered1(package);
- altered1[50] += 1;
- ASSERT_EQ(VERIFY_FAILURE,
- verify_file(reinterpret_cast<const unsigned char*>(altered1.data()), altered1.size(),
- certs));
-
- std::string altered2(package);
- altered2[10] += 1;
- ASSERT_EQ(VERIFY_FAILURE,
- verify_file(reinterpret_cast<const unsigned char*>(altered2.data()), altered2.size(),
- 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, nullptr), VERIFY_SUCCESS);
-}
-
-TEST_P(VerifierFailureTest, VerifyFailure) {
- 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_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_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_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_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", "v1"}),
- std::vector<std::string>({"fake-eocd.zip", "v1"})));
diff --git a/tests/manual/recovery_test.cpp b/tests/manual/recovery_test.cpp
index 64e3b59e6..e1d0771e7 100644
--- a/tests/manual/recovery_test.cpp
+++ b/tests/manual/recovery_test.cpp
@@ -14,27 +14,22 @@
* limitations under the License.
*/
-#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
+#include <memory>
#include <string>
-#include <vector>
#include <android-base/file.h>
-#include <android-base/strings.h>
#include <android/log.h>
#include <gtest/gtest.h>
-#include <png.h>
#include <private/android_logger.h>
-#include "minui/minui.h"
-
-static const std::string myFilename = "/data/misc/recovery/inject.txt";
-static const std::string myContent = "Hello World\nWelcome to my recovery\n";
-static const std::string kLocale = "zu";
-static const std::string kResourceTestDir = "/data/nativetest/recovery/";
+static const std::string kInjectTxtFilename = "/data/misc/recovery/inject.txt";
+static const std::string kInjectTxtContent = "Hello World\nWelcome to my recovery\n";
// Failure is expected on systems that do not deliver either the
// recovery-persist or recovery-refresh executables. Tests also require
@@ -44,9 +39,9 @@ static ssize_t __pmsg_fn(log_id_t logId, char prio, const char *filename,
const char *buf, size_t len, void *arg) {
EXPECT_EQ(LOG_ID_SYSTEM, logId);
EXPECT_EQ(ANDROID_LOG_INFO, prio);
- EXPECT_NE(std::string::npos, myFilename.find(filename));
- EXPECT_EQ(myContent, buf);
- EXPECT_EQ(myContent.size(), len);
+ EXPECT_NE(std::string::npos, kInjectTxtFilename.find(filename));
+ EXPECT_EQ(kInjectTxtContent, buf);
+ EXPECT_EQ(kInjectTxtContent.size(), len);
EXPECT_EQ(nullptr, arg);
return len;
}
@@ -59,13 +54,14 @@ TEST(recovery, refresh) {
ssize_t ret = __android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
if (ret == -ENOENT) {
- EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
- myFilename.c_str(), myContent.c_str(), myContent.size()));
+ EXPECT_LT(0, __android_log_pmsg_file_write(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(),
+ kInjectTxtContent.c_str(), kInjectTxtContent.size()));
- fprintf(stderr, "injected test data, requires two intervening reboots "
- "to check for replication\n");
+ fprintf(stderr,
+ "injected test data, requires two intervening reboots to check for replication\n");
}
- EXPECT_EQ(static_cast<ssize_t>(myContent.size()), ret);
+ EXPECT_EQ(static_cast<ssize_t>(kInjectTxtContent.size()), ret);
}
// recovery.persist - Requires recovery.inject, then a reboot, then
@@ -76,149 +72,18 @@ TEST(recovery, persist) {
ssize_t ret = __android_log_pmsg_file_read(
LOG_ID_SYSTEM, ANDROID_LOG_INFO, "recovery/", __pmsg_fn, nullptr);
if (ret == -ENOENT) {
- EXPECT_LT(0, __android_log_pmsg_file_write(LOG_ID_SYSTEM, ANDROID_LOG_INFO,
- myFilename.c_str(), myContent.c_str(), myContent.size()));
+ EXPECT_LT(0, __android_log_pmsg_file_write(
+ LOG_ID_SYSTEM, ANDROID_LOG_INFO, kInjectTxtFilename.c_str(),
+ kInjectTxtContent.c_str(), kInjectTxtContent.size()));
- fprintf(stderr, "injected test data, requires intervening reboot "
- "to check for storage\n");
+ fprintf(stderr, "injected test data, requires intervening reboot to check for storage\n");
}
std::string buf;
- EXPECT_TRUE(android::base::ReadFileToString(myFilename, &buf));
- EXPECT_EQ(myContent, buf);
- if (access(myFilename.c_str(), F_OK) == 0) {
- fprintf(stderr, "Removing persistent test data, "
- "check if reconstructed on reboot\n");
- }
- EXPECT_EQ(0, unlink(myFilename.c_str()));
-}
-
-std::vector<std::string> image_dir {
- "res-mdpi/images/",
- "res-hdpi/images/",
- "res-xhdpi/images/",
- "res-xxhdpi/images/",
- "res-xxxhdpi/images/"
-};
-
-static int png_filter(const dirent* de) {
- if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
- return 0;
+ EXPECT_TRUE(android::base::ReadFileToString(kInjectTxtFilename, &buf));
+ EXPECT_EQ(kInjectTxtContent, buf);
+ if (access(kInjectTxtFilename.c_str(), F_OK) == 0) {
+ fprintf(stderr, "Removing persistent test data, check if reconstructed on reboot\n");
}
- return 1;
+ EXPECT_EQ(0, unlink(kInjectTxtFilename.c_str()));
}
-
-// Find out all png files to test under /data/nativetest/recovery/.
-static std::vector<std::string> add_files() {
- std::vector<std::string> files;
- for (const std::string& str : image_dir) {
- std::string dir_path = kResourceTestDir + str;
- dirent** namelist;
- int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
- if (n == -1) {
- printf("Failed to scan dir %s: %s\n", kResourceTestDir.c_str(), strerror(errno));
- return files;
- }
- if (n == 0) {
- printf("No file is added for test in %s\n", kResourceTestDir.c_str());
- }
-
- while (n--) {
- std::string file_path = dir_path + namelist[n]->d_name;
- files.push_back(file_path);
- free(namelist[n]);
- }
- free(namelist);
- }
- return files;
-}
-
-class ResourceTest : public testing::TestWithParam<std::string> {
- public:
- static std::vector<std::string> png_list;
-
- // Parse a png file and test if it's qualified for the background text image
- // under recovery.
- void SetUp() override {
- std::string file_path = GetParam();
- fp = fopen(file_path.c_str(), "rbe");
- ASSERT_NE(nullptr, fp);
-
- unsigned char header[8];
- size_t bytesRead = fread(header, 1, sizeof(header), fp);
- ASSERT_EQ(sizeof(header), bytesRead);
- ASSERT_EQ(0, png_sig_cmp(header, 0, sizeof(header)));
-
- png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
- ASSERT_NE(nullptr, png_ptr);
-
- info_ptr = png_create_info_struct(png_ptr);
- ASSERT_NE(nullptr, info_ptr);
-
- png_init_io(png_ptr, fp);
- png_set_sig_bytes(png_ptr, sizeof(header));
- png_read_info(png_ptr, info_ptr);
-
- int color_type, bit_depth;
- png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, nullptr, nullptr,
- nullptr);
- ASSERT_EQ(PNG_COLOR_TYPE_GRAY, color_type) << "Recovery expects grayscale PNG file.";
- ASSERT_LT(static_cast<png_uint_32>(5), width);
- ASSERT_LT(static_cast<png_uint_32>(0), height);
- if (bit_depth <= 8) {
- // 1-, 2-, 4-, or 8-bit gray images: expand to 8-bit gray.
- png_set_expand_gray_1_2_4_to_8(png_ptr);
- }
-
- png_byte channels = png_get_channels(png_ptr, info_ptr);
- ASSERT_EQ(1, channels) << "Recovery background text images expects 1-channel PNG file.";
- }
-
- void TearDown() override {
- if (png_ptr != nullptr && info_ptr != nullptr) {
- png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
- }
-
- if (fp != nullptr) {
- fclose(fp);
- }
- }
-
- protected:
- png_structp png_ptr;
- png_infop info_ptr;
- png_uint_32 width, height;
-
- FILE* fp;
-};
-
-std::vector<std::string> ResourceTest::png_list = add_files();
-
-TEST_P(ResourceTest, ValidateLocale) {
- std::vector<unsigned char> row(width);
- for (png_uint_32 y = 0; y < height; ++y) {
- png_read_row(png_ptr, row.data(), nullptr);
- int w = (row[1] << 8) | row[0];
- int h = (row[3] << 8) | row[2];
- int len = row[4];
- EXPECT_LT(0, w);
- EXPECT_LT(0, h);
- EXPECT_LT(0, len) << "Locale string should be non-empty.";
- EXPECT_NE(0, row[5]) << "Locale string is missing.";
-
- ASSERT_GT(height, y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
- char* loc = reinterpret_cast<char*>(&row[5]);
- if (matches_locale(loc, kLocale.c_str())) {
- EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
- break;
- } else {
- for (int i = 0; i < h; ++i, ++y) {
- png_read_row(png_ptr, row.data(), nullptr);
- }
- }
- }
-}
-
-INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourceTest,
- ::testing::ValuesIn(ResourceTest::png_list.cbegin(),
- ResourceTest::png_list.cend()));
diff --git a/tests/testdata/battery_scale.png b/tests/testdata/battery_scale.png
new file mode 100644
index 000000000..2ae8f0fd7
--- /dev/null
+++ b/tests/testdata/battery_scale.png
Binary files differ
diff --git a/tests/testdata/font.png b/tests/testdata/font.png
new file mode 100644
index 000000000..d95408a93
--- /dev/null
+++ b/tests/testdata/font.png
Binary files differ
diff --git a/tests/testdata/gzipped_source b/tests/testdata/gzipped_source
new file mode 100644
index 000000000..6d425d059
--- /dev/null
+++ b/tests/testdata/gzipped_source
Binary files differ
diff --git a/tests/testdata/gzipped_target b/tests/testdata/gzipped_target
new file mode 100644
index 000000000..562126286
--- /dev/null
+++ b/tests/testdata/gzipped_target
Binary files differ
diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip
deleted file mode 100644
index 8b1ef8bdd..000000000
--- a/tests/testdata/jarsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/loop00000.png b/tests/testdata/loop00000.png
new file mode 100644
index 000000000..0e11c0100
--- /dev/null
+++ b/tests/testdata/loop00000.png
Binary files differ
diff --git a/tests/testdata/new.file b/tests/testdata/new.file
deleted file mode 100644
index cdeb8fd50..000000000
--- a/tests/testdata/new.file
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/old.file b/tests/testdata/old.file
deleted file mode 100644
index 166c8732e..000000000
--- a/tests/testdata/old.file
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/otasigned_4096bits.zip b/tests/testdata/otasigned_4096bits.zip
new file mode 100644
index 000000000..5016dfc9a
--- /dev/null
+++ b/tests/testdata/otasigned_4096bits.zip
Binary files differ
diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff
deleted file mode 100644
index b78d38573..000000000
--- a/tests/testdata/patch.bsdiff
+++ /dev/null
Binary files differ
diff --git a/tests/testdata/testkey_4096bits.x509.pem b/tests/testdata/testkey_4096bits.x509.pem
new file mode 100644
index 000000000..cba30d61b
--- /dev/null
+++ b/tests/testdata/testkey_4096bits.x509.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIGADCCA+igAwIBAgIJAJiRMVvanGUaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD
+VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g
+VmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE
+AwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe
+Fw0xODEwMzAxMjEzNTFaFw00NjAzMTcxMjEzNTFaMIGUMQswCQYDVQQGEwJVUzET
+MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G
+A1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p
+ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJKoZI
+hvcNAQEBBQADggIPADCCAgoCggIBAL3ghKA8Gz9qOORY8gMY4wlB2tCJLDUO2tFG
+LVK1UphtQMp+YEcz/0VQKVV7de7z6V4EMQ5P1HbxHOsjcKn/zXAl4YgFt7b5kZbC
+bpNK4CYHEfho3j6fpYtq5d9q8rIA2kI0uZkkqPy1zXKTl2C2PjOoAnLQRk5xBVQG
+M10/wYsf7yX36mSWoJJwKPp/EzVFpA+hX8HpljeIiZ6CFzKwJdqv9zO/xzfp6NsX
+Tv5EGdkDxmw3qQqKgyl8dLMTZ/2zNfvVOMeZDusEPDF7A/lbU1byLWrKQdCzVb40
+yc7BCSRGYwM29R/byOcgD+lslwKSGzgzNmQXICt1tXz9bSJR8qh4tlAaiRc3ZKBe
+hJWIFGkGtD/cDGtDE5DbNAOz6CdSDdE2XN0Qf0cfN1RHVE6fo2FtFicRRVuFBt8M
+2cbQ7bzmEvtHD6W6dsf120FH7gppXKmnhMx1WazpxR2QltbiYDTy2ZZi4paS/jDB
+fL9gMCWp3Ohg2y74NGfUw5CQWQsDpcki6I7RvwClBCyOV51LHn5LE/nY4DkVrZxk
+Pw0/YrTWz5J5PbdMetTuIunE4ec4lm8nZnh1ET+2MHx2+RoyF5vBs4rp1KHHRaEA
+veD2AfQOWxz7kOG9+akFot7n+QoWEGdwY0mJ9jsO/IITCjv3VbD7o0OoJv1R2AW5
+sK2KQ4PDAgMBAAGjUzBRMB0GA1UdDgQWBBT2EbrayXGhY6VCvSlLtRNyjW9ceDAf
+BgNVHSMEGDAWgBT2EbrayXGhY6VCvSlLtRNyjW9ceDAPBgNVHRMBAf8EBTADAQH/
+MA0GCSqGSIb3DQEBCwUAA4ICAQC7SsWap9zDKmuR0qMUZ6wlualnag0hUG1jZHQP
+t63KO6LmNNMSuXRX60Zcq6WWzgLOyoT4HqHZZ47Jamfb4XQQcnWMMW0tJ3pDtTkz
+dZILBInHJO8QPYI8Du6XWsDLSvMajq6ueBtO3NdcgsNL7eiHf3WoOtajLZxFM94Z
+MESkUQOIsqHolYeTMHLTsuGkX1CK2Zw3Xn18bUSTYwZCHa6mYH00ItUBfetGCnWh
+Y7bth/R15Cc+hocSB7ZsOa/R5kDyDdFDIKrnV5nH5Yd7CryrYC6Ac5UarYrxSJTq
+eKPwqUlJB/tJW/lvdLt8YaURbFGzf/ZqU12zZRafYjmMjcQvfpzMoDSnbvHTA9IR
+ZGO7dwhwykoSaL4/8LWde49xQUq6F2pQBRmEr+7mTzml1MaM5cWEk5emkCMXgLog
+k+c56CAk1EdM1teWik7wR0TIqkkYyYJHTSg61GkXUIXrZJ6iYx2ejDg1+QTPm9rU
+Yr7nP52gVkQuUAX1+xB6wKLSDizQJw8SNiUGXl5+2vwV6+0BI3/CXlQ8I/nRPBC1
+oqOIkRSbE+IF7DP9QvYuNG/3bZZQ8LUVeHxqI5Mq8K2VIJZd95AIwPNMH34SaDGz
+9xjG28Fq4ZkuDP0pCsHM9d2XEwK5PEVS18WW5fJ/QcJKMno4IPTB70ZBBjVzv6Y+
+MYjOrw==
+-----END CERTIFICATE-----
diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip
deleted file mode 100644
index 24e3eadac..000000000
--- a/tests/testdata/unsigned.zip
+++ /dev/null
Binary files differ
diff --git a/tests/unit/applypatch_modes_test.cpp b/tests/unit/applypatch_modes_test.cpp
new file mode 100644
index 000000000..08414b796
--- /dev/null
+++ b/tests/unit/applypatch_modes_test.cpp
@@ -0,0 +1,198 @@
+/*
+ * 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" }));
+}
diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp
new file mode 100644
index 000000000..794f2c103
--- /dev/null
+++ b/tests/unit/applypatch_test.cpp
@@ -0,0 +1,290 @@
+/*
+ * 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 <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "applypatch/applypatch.h"
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+
+using namespace std::string_literals;
+
+class ApplyPatchTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ source_file = from_testdata_base("boot.img");
+ FileContents boot_fc;
+ ASSERT_TRUE(LoadFileContents(source_file, &boot_fc));
+ source_size = boot_fc.data.size();
+ source_sha1 = print_sha1(boot_fc.sha1);
+
+ target_file = from_testdata_base("recovery.img");
+ FileContents recovery_fc;
+ ASSERT_TRUE(LoadFileContents(target_file, &recovery_fc));
+ target_size = recovery_fc.data.size();
+ target_sha1 = print_sha1(recovery_fc.sha1);
+
+ source_partition = Partition(source_file, source_size, source_sha1);
+ target_partition = Partition(partition_file.path, target_size, target_sha1);
+
+ srand(time(nullptr));
+ bad_sha1_a = android::base::StringPrintf("%040x", rand());
+ bad_sha1_b = android::base::StringPrintf("%040x", rand());
+
+ // Reset the cache backup file.
+ Paths::Get().set_cache_temp_source(cache_temp_source.path);
+ }
+
+ void TearDown() override {
+ ASSERT_TRUE(android::base::RemoveFileIfExists(cache_temp_source.path));
+ }
+
+ std::string source_file;
+ std::string source_sha1;
+ size_t source_size;
+
+ std::string target_file;
+ std::string target_sha1;
+ size_t target_size;
+
+ std::string bad_sha1_a;
+ std::string bad_sha1_b;
+
+ Partition source_partition;
+ Partition target_partition;
+
+ private:
+ TemporaryFile partition_file;
+ TemporaryFile cache_temp_source;
+};
+
+TEST_F(ApplyPatchTest, CheckPartition) {
+ ASSERT_TRUE(CheckPartition(source_partition));
+}
+
+TEST_F(ApplyPatchTest, CheckPartition_Mismatching) {
+ ASSERT_FALSE(CheckPartition(Partition(source_file, target_size, target_sha1)));
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size, bad_sha1_a)));
+
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size - 1, source_sha1)));
+ ASSERT_FALSE(CheckPartition(Partition(source_file, source_size + 1, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck) {
+ ASSERT_TRUE(PatchPartitionCheck(target_partition, source_partition));
+
+ ASSERT_TRUE(
+ PatchPartitionCheck(Partition(source_file, source_size - 1, source_sha1), source_partition));
+
+ ASSERT_TRUE(
+ PatchPartitionCheck(Partition(source_file, source_size + 1, source_sha1), source_partition));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup) {
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+
+ Paths::Get().set_cache_temp_source(source_file);
+ ASSERT_TRUE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartitionCheck_UseBackup_BothCorrupted) {
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+
+ Paths::Get().set_cache_temp_source(target_file);
+ ASSERT_FALSE(
+ PatchPartitionCheck(target_partition, Partition(target_file, source_size, source_sha1)));
+}
+
+TEST_F(ApplyPatchTest, PatchPartition) {
+ FileContents patch_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot.p"), &patch_fc));
+ Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
+
+ FileContents bonus_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("bonus.file"), &bonus_fc));
+ Value bonus(Value::Type::BLOB, std::string(bonus_fc.data.cbegin(), bonus_fc.data.cend()));
+
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, &bonus));
+}
+
+// Tests patching an eMMC target without a separate bonus file (i.e. recovery-from-boot patch has
+// everything).
+TEST_F(ApplyPatchTest, PatchPartitionWithoutBonusFile) {
+ FileContents patch_fc;
+ ASSERT_TRUE(LoadFileContents(from_testdata_base("recovery-from-boot-with-bonus.p"), &patch_fc));
+ Value patch(Value::Type::BLOB, std::string(patch_fc.data.cbegin(), patch_fc.data.cend()));
+
+ ASSERT_TRUE(PatchPartition(target_partition, source_partition, patch, nullptr));
+}
+
+class FreeCacheTest : public ::testing::Test {
+ protected:
+ static constexpr size_t PARTITION_SIZE = 4096 * 10;
+
+ // Returns a sorted list of files in |dirname|.
+ static std::vector<std::string> FindFilesInDir(const std::string& dirname) {
+ std::vector<std::string> file_list;
+
+ std::unique_ptr<DIR, decltype(&closedir)> d(opendir(dirname.c_str()), closedir);
+ struct dirent* de;
+ while ((de = readdir(d.get())) != 0) {
+ std::string path = dirname + "/" + de->d_name;
+
+ struct stat st;
+ if (stat(path.c_str(), &st) == 0 && S_ISREG(st.st_mode)) {
+ file_list.emplace_back(de->d_name);
+ }
+ }
+
+ std::sort(file_list.begin(), file_list.end());
+ return file_list;
+ }
+
+ void AddFilesToDir(const std::string& dir, const std::vector<std::string>& files) {
+ std::string zeros(4096, 0);
+ for (const auto& file : files) {
+ temporary_files_.push_back(dir + "/" + file);
+ ASSERT_TRUE(android::base::WriteStringToFile(zeros, temporary_files_.back()));
+ }
+ }
+
+ void SetUp() override {
+ Paths::Get().set_cache_log_directory(mock_log_dir.path);
+ temporary_files_.clear();
+ }
+
+ void TearDown() override {
+ for (const auto& file : temporary_files_) {
+ ASSERT_TRUE(android::base::RemoveFileIfExists(file));
+ }
+ }
+
+ // A mock method to calculate the free space. It assumes the partition has a total size of 40960
+ // bytes and all files are 4096 bytes in size.
+ static size_t MockFreeSpaceChecker(const std::string& dirname) {
+ std::vector<std::string> files = FindFilesInDir(dirname);
+ return PARTITION_SIZE - 4096 * files.size();
+ }
+
+ TemporaryDir mock_cache;
+ TemporaryDir mock_log_dir;
+
+ private:
+ std::vector<std::string> temporary_files_;
+};
+
+TEST_F(FreeCacheTest, FreeCacheSmoke) {
+ std::vector<std::string> files = { "file1", "file2", "file3" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_cache.path, MockFreeSpaceChecker));
+
+ ASSERT_EQ(std::vector<std::string>{ "file3" }, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_cache.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheFreeSpaceCheckerError) {
+ std::vector<std::string> files{ "file1", "file2", "file3" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 7, MockFreeSpaceChecker(mock_cache.path));
+
+ ASSERT_FALSE(
+ RemoveFilesInDirectory(4096 * 9, mock_cache.path, [](const std::string&) { return -1; }));
+}
+
+TEST_F(FreeCacheTest, FreeCacheOpenFile) {
+ std::vector<std::string> files = { "file1", "file2" };
+ AddFilesToDir(mock_cache.path, files);
+ ASSERT_EQ(files, FindFilesInDir(mock_cache.path));
+ ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_cache.path));
+
+ std::string file1_path = mock_cache.path + "/file1"s;
+ android::base::unique_fd fd(open(file1_path.c_str(), O_RDONLY));
+
+ // file1 can't be deleted as it's opened by us.
+ ASSERT_FALSE(RemoveFilesInDirectory(4096 * 10, mock_cache.path, MockFreeSpaceChecker));
+
+ ASSERT_EQ(std::vector<std::string>{ "file1" }, FindFilesInDir(mock_cache.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsSmoke) {
+ std::vector<std::string> log_files = { "last_log", "last_log.1", "last_kmsg.2", "last_log.5",
+ "last_log.10" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Logs with a higher index will be deleted first
+ std::vector<std::string> expected = { "last_log", "last_log.1" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+ ASSERT_EQ(4096 * 8, MockFreeSpaceChecker(mock_log_dir.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsStringComparison) {
+ std::vector<std::string> log_files = { "last_log.1", "last_kmsg.1", "last_log.not_number",
+ "last_kmsgrandom" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 6, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_TRUE(RemoveFilesInDirectory(4096 * 9, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Logs with incorrect format will be deleted first; and the last_kmsg with the same index is
+ // deleted before last_log.
+ std::vector<std::string> expected = { "last_log.1" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+ ASSERT_EQ(4096 * 9, MockFreeSpaceChecker(mock_log_dir.path));
+}
+
+TEST_F(FreeCacheTest, FreeCacheLogsOtherFiles) {
+ std::vector<std::string> log_files = { "last_install", "command", "block.map", "last_log",
+ "last_kmsg.1" };
+ AddFilesToDir(mock_log_dir.path, log_files);
+ ASSERT_EQ(4096 * 5, MockFreeSpaceChecker(mock_log_dir.path));
+
+ ASSERT_FALSE(RemoveFilesInDirectory(4096 * 8, mock_log_dir.path, MockFreeSpaceChecker));
+
+ // Non log files in /cache/recovery won't be deleted.
+ std::vector<std::string> expected = { "block.map", "command", "last_install" };
+ ASSERT_EQ(expected, FindFilesInDir(mock_log_dir.path));
+}
diff --git a/tests/unit/asn1_decoder_test.cpp b/tests/unit/asn1_decoder_test.cpp
index b334a655b..d94dd4353 100644
--- a/tests/unit/asn1_decoder_test.cpp
+++ b/tests/unit/asn1_decoder_test.cpp
@@ -20,7 +20,7 @@
#include <gtest/gtest.h>
-#include "asn1_decoder.h"
+#include "private/asn1_decoder.h"
TEST(Asn1DecoderTest, Empty_Failure) {
uint8_t empty[] = {};
diff --git a/tests/component/bootloader_message_test.cpp b/tests/unit/bootloader_message_test.cpp
index 6cc59a495..b005d199c 100644
--- a/tests/component/bootloader_message_test.cpp
+++ b/tests/unit/bootloader_message_test.cpp
@@ -17,8 +17,8 @@
#include <string>
#include <vector>
+#include <android-base/file.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <bootloader_message/bootloader_message.h>
#include <gtest/gtest.h>
diff --git a/tests/unit/commands_test.cpp b/tests/unit/commands_test.cpp
new file mode 100644
index 000000000..8a54df703
--- /dev/null
+++ b/tests/unit/commands_test.cpp
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <string>
+
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <openssl/sha.h>
+
+#include "otautil/print_sha1.h"
+#include "otautil/rangeset.h"
+#include "private/commands.h"
+
+TEST(CommandsTest, ParseType) {
+ ASSERT_EQ(Command::Type::ZERO, Command::ParseType("zero"));
+ ASSERT_EQ(Command::Type::NEW, Command::ParseType("new"));
+ ASSERT_EQ(Command::Type::ERASE, Command::ParseType("erase"));
+ ASSERT_EQ(Command::Type::MOVE, Command::ParseType("move"));
+ ASSERT_EQ(Command::Type::BSDIFF, Command::ParseType("bsdiff"));
+ ASSERT_EQ(Command::Type::IMGDIFF, Command::ParseType("imgdiff"));
+ ASSERT_EQ(Command::Type::STASH, Command::ParseType("stash"));
+ ASSERT_EQ(Command::Type::FREE, Command::ParseType("free"));
+ ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, Command::ParseType("compute_hash_tree"));
+}
+
+TEST(CommandsTest, ParseType_InvalidCommand) {
+ ASSERT_EQ(Command::Type::LAST, Command::ParseType("foo"));
+ ASSERT_EQ(Command::Type::LAST, Command::ParseType("bar"));
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksOnly) {
+ const std::vector<std::string> tokens{
+ "4,569884,569904,591946,592043",
+ "117",
+ "4,566779,566799,591946,592043",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
+ target);
+ ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), {}, {}),
+ source);
+ ASSERT_EQ(117, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_StashesOnly) {
+ const std::vector<std::string> tokens{
+ "2,350729,350731",
+ "2",
+ "-",
+ "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15:2,0,2",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", &target,
+ "1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", &source, &err));
+ ASSERT_EQ(
+ TargetInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 350729, 350731 } })),
+ target);
+ ASSERT_EQ(
+ SourceInfo("1c25ba04d3278d6b65a1b9f17abac78425ec8b8d", {}, {},
+ {
+ StashInfo("6ebcf8cf1f6be0bc49e7d4a864214251925d1d15", RangeSet({ { 0, 2 } })),
+ }),
+ source);
+ ASSERT_EQ(2, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_SourceBlocksAndStashes) {
+ const std::vector<std::string> tokens{
+ "4,611641,611643,636981,637075",
+ "96",
+ "4,636981,637075,770665,770666",
+ "4,0,94,95,96",
+ "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_TRUE(Command::ParseTargetInfoAndSourceInfo(
+ tokens, "4734d1b241eb3d0f993714aaf7d665fae43772b6", &target,
+ "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d", &source, &err));
+ ASSERT_EQ(TargetInfo("4734d1b241eb3d0f993714aaf7d665fae43772b6",
+ RangeSet({ { 611641, 611643 }, { 636981, 637075 } })),
+ target);
+ ASSERT_EQ(SourceInfo(
+ "a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 636981, 637075 }, { 770665, 770666 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ {
+ StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23", RangeSet({ { 94, 95 } })),
+ }),
+ source);
+ ASSERT_EQ(96, source.blocks());
+}
+
+TEST(CommandsTest, ParseTargetInfoAndSourceInfo_InvalidInput) {
+ const std::vector<std::string> tokens{
+ "4,611641,611643,636981,637075",
+ "96",
+ "4,636981,637075,770665,770666",
+ "4,0,94,95,96",
+ "9eedf00d11061549e32503cadf054ec6fbfa7a23:2,94,95",
+ };
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+
+ // Mismatching block count.
+ {
+ std::vector<std::string> tokens_copy(tokens);
+ tokens_copy[1] = "97";
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+
+ // Excess stashes (causing block count mismatch).
+ {
+ std::vector<std::string> tokens_copy(tokens);
+ tokens_copy.push_back("e145a2f83a33334714ac65e34969c1f115e54a6f:2,0,22");
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ tokens_copy, "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+
+ // Invalid args.
+ for (size_t i = 0; i < tokens.size(); i++) {
+ TargetInfo target;
+ SourceInfo source;
+ std::string err;
+ ASSERT_FALSE(Command::ParseTargetInfoAndSourceInfo(
+ std::vector<std::string>(tokens.cbegin() + i + 1, tokens.cend()),
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &target,
+ "1d74d1a60332fd38cf9405f1bae67917888da6cb", &source, &err));
+ }
+}
+
+TEST(CommandsTest, Parse_EmptyInput) {
+ std::string err;
+ ASSERT_FALSE(Command::Parse("", 0, &err));
+ ASSERT_EQ("invalid type", err);
+}
+
+TEST(CommandsTest, Parse_ABORT_Allowed) {
+ Command::abort_allowed_ = true;
+
+ const std::string input{ "abort" };
+ std::string err;
+ Command command = Command::Parse(input, 0, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_ABORT_NotAllowed) {
+ const std::string input{ "abort" };
+ std::string err;
+ Command command = Command::Parse(input, 0, &err);
+ ASSERT_FALSE(command);
+}
+
+TEST(CommandsTest, Parse_BSDIFF) {
+ const std::string input{
+ "bsdiff 0 148 "
+ "f201a4e04bd3860da6ad47b957ef424d58a58f8c 9d5d223b4bc5c45dbd25a799c4f1a98466731599 "
+ "4,565704,565752,566779,566799 "
+ "68 4,64525,64545,565704,565752"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 1, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::BSDIFF, command.type());
+ ASSERT_EQ(1, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("9d5d223b4bc5c45dbd25a799c4f1a98466731599",
+ RangeSet({ { 565704, 565752 }, { 566779, 566799 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("f201a4e04bd3860da6ad47b957ef424d58a58f8c",
+ RangeSet({ { 64525, 64545 }, { 565704, 565752 } }), RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(0, 148), command.patch());
+}
+
+TEST(CommandsTest, Parse_ERASE) {
+ const std::string input{ "erase 2,5,10" };
+ std::string err;
+ Command command = Command::Parse(input, 2, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::ERASE, command.type());
+ ASSERT_EQ(2, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 5, 10 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_FREE) {
+ const std::string input{ "free hash1" };
+ std::string err;
+ Command command = Command::Parse(input, 3, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::FREE, command.type());
+ ASSERT_EQ(3, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo("hash1", RangeSet()), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_IMGDIFF) {
+ const std::string input{
+ "imgdiff 29629269 185 "
+ "a6b1c49aed1b57a2aab1ec3e1505b945540cd8db 51978f65035f584a8ef7afa941dacb6d5e862164 "
+ "2,90851,90852 "
+ "1 2,90851,90852"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 4, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::IMGDIFF, command.type());
+ ASSERT_EQ(4, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("51978f65035f584a8ef7afa941dacb6d5e862164", RangeSet({ { 90851, 90852 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("a6b1c49aed1b57a2aab1ec3e1505b945540cd8db", RangeSet({ { 90851, 90852 } }),
+ RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(29629269, 185), command.patch());
+}
+
+TEST(CommandsTest, Parse_MOVE) {
+ const std::string input{
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb "
+ "4,569884,569904,591946,592043 117 4,566779,566799,591946,592043"
+ };
+ std::string err;
+ Command command = Command::Parse(input, 5, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::MOVE, command.type());
+ ASSERT_EQ(5, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 569884, 569904 }, { 591946, 592043 } })),
+ command.target());
+ ASSERT_EQ(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 566779, 566799 }, { 591946, 592043 } }), RangeSet(), {}),
+ command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_NEW) {
+ const std::string input{ "new 4,3,5,10,12" };
+ std::string err;
+ Command command = Command::Parse(input, 6, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::NEW, command.type());
+ ASSERT_EQ(6, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 3, 5 }, { 10, 12 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_STASH) {
+ const std::string input{ "stash hash1 2,5,10" };
+ std::string err;
+ Command command = Command::Parse(input, 7, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::STASH, command.type());
+ ASSERT_EQ(7, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo("hash1", RangeSet({ { 5, 10 } })), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_ZERO) {
+ const std::string input{ "zero 2,1,5" };
+ std::string err;
+ Command command = Command::Parse(input, 8, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::ZERO, command.type());
+ ASSERT_EQ(8, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ ASSERT_EQ(TargetInfo("unknown-hash", RangeSet({ { 1, 5 } })), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_COMPUTE_HASH_TREE) {
+ const std::string input{ "compute_hash_tree 2,0,1 2,3,4 sha1 unknown-salt unknown-root-hash" };
+ std::string err;
+ Command command = Command::Parse(input, 9, &err);
+ ASSERT_TRUE(command);
+
+ ASSERT_EQ(Command::Type::COMPUTE_HASH_TREE, command.type());
+ ASSERT_EQ(9, command.index());
+ ASSERT_EQ(input, command.cmdline());
+
+ HashTreeInfo expected_info(RangeSet({ { 0, 1 } }), RangeSet({ { 3, 4 } }), "sha1", "unknown-salt",
+ "unknown-root-hash");
+ ASSERT_EQ(expected_info, command.hash_tree_info());
+ ASSERT_EQ(TargetInfo(), command.target());
+ ASSERT_EQ(SourceInfo(), command.source());
+ ASSERT_EQ(StashInfo(), command.stash());
+ ASSERT_EQ(PatchInfo(), command.patch());
+}
+
+TEST(CommandsTest, Parse_InvalidNumberOfArgs) {
+ Command::abort_allowed_ = true;
+
+ // Note that the case of having excess args in BSDIFF, IMGDIFF and MOVE is covered by
+ // ParseTargetInfoAndSourceInfo_InvalidInput.
+ std::vector<std::string> inputs{
+ "abort foo",
+ "bsdiff",
+ "compute_hash_tree, 2,0,1 2,0,1 unknown-algorithm unknown-salt",
+ "erase",
+ "erase 4,3,5,10,12 hash1",
+ "free",
+ "free id1 id2",
+ "imgdiff",
+ "move",
+ "new",
+ "new 4,3,5,10,12 hash1",
+ "stash",
+ "stash id1",
+ "stash id1 4,3,5,10,12 id2",
+ "zero",
+ "zero 4,3,5,10,12 hash2",
+ };
+ for (const auto& input : inputs) {
+ std::string err;
+ ASSERT_FALSE(Command::Parse(input, 0, &err));
+ }
+}
+
+TEST(SourceInfoTest, Overlaps) {
+ ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }))));
+
+ ASSERT_TRUE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 4, 7 }, { 16, 23 } }))));
+
+ ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 9, 16 } }))));
+}
+
+TEST(SourceInfoTest, Overlaps_EmptySourceOrTarget) {
+ ASSERT_FALSE(SourceInfo().Overlaps(TargetInfo()));
+
+ ASSERT_FALSE(SourceInfo().Overlaps(
+ TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb", RangeSet({ { 7, 9 }, { 16, 20 } }))));
+
+ ASSERT_FALSE(SourceInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 7, 9 }, { 16, 20 } }), {}, {})
+ .Overlaps(TargetInfo()));
+}
+
+TEST(SourceInfoTest, Overlaps_WithStashes) {
+ ASSERT_FALSE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
+ RangeSet({ { 94, 95 } })) })
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 175, 265 } }))));
+
+ ASSERT_TRUE(SourceInfo("a6cbdf3f416960f02189d3a814ec7e9e95c44a0d",
+ RangeSet({ { 81, 175 }, { 265, 266 } }), // source ranges
+ RangeSet({ { 0, 94 }, { 95, 96 } }), // source location
+ { StashInfo("9eedf00d11061549e32503cadf054ec6fbfa7a23",
+ RangeSet({ { 94, 95 } })) })
+ .Overlaps(TargetInfo("1d74d1a60332fd38cf9405f1bae67917888da6cb",
+ RangeSet({ { 265, 266 } }))));
+}
+
+// The block size should be specified by the caller of ReadAll (i.e. from Command instance during
+// normal run).
+constexpr size_t kBlockSize = 4096;
+
+TEST(SourceInfoTest, ReadAll) {
+ // "2727756cfee3fbfe24bf5650123fd7743d7b3465" is the SHA-1 hex digest of 8192 * 'a'.
+ const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
+ {});
+ auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
+ std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
+ return 0;
+ };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+ ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(buffer.data(), buffer.size(), digest);
+ ASSERT_EQ(source.hash(), print_sha1(digest));
+}
+
+TEST(SourceInfoTest, ReadAll_WithStashes) {
+ const SourceInfo source(
+ // SHA-1 hex digest of 8192 * 'a' + 4096 * 'b'.
+ "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
+ { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
+ auto block_reader = [](const RangeSet& src, std::vector<uint8_t>* block_buffer) -> int {
+ std::fill_n(block_buffer->begin(), src.blocks() * kBlockSize, 'a');
+ return 0;
+ };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>* stash_buffer) -> int {
+ std::fill_n(stash_buffer->begin(), kBlockSize, 'b');
+ return 0;
+ };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ ASSERT_TRUE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+ ASSERT_EQ(source.blocks() * kBlockSize, buffer.size());
+
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(buffer.data(), buffer.size(), digest);
+ ASSERT_EQ(source.hash(), print_sha1(digest));
+}
+
+TEST(SourceInfoTest, ReadAll_BufferTooSmall) {
+ const SourceInfo source("2727756cfee3fbfe24bf5650123fd7743d7b3465", RangeSet({ { 0, 2 } }), {},
+ {});
+ auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize - 1);
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, stash_reader));
+}
+
+TEST(SourceInfoTest, ReadAll_FailingReader) {
+ const SourceInfo source(
+ "ee3ebea26130769c10ad13604712100346d48660", RangeSet({ { 0, 2 } }), RangeSet({ { 0, 2 } }),
+ { StashInfo("1e41f7a59e80c6eb4dc043caae80d273f130bed8", RangeSet({ { 2, 3 } })) });
+ std::vector<uint8_t> buffer(source.blocks() * kBlockSize);
+ auto failing_block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return -1; };
+ auto stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return 0; };
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, failing_block_reader, stash_reader));
+
+ auto block_reader = [](const RangeSet&, std::vector<uint8_t>*) -> int { return 0; };
+ auto failing_stash_reader = [](const std::string&, std::vector<uint8_t>*) -> int { return -1; };
+ ASSERT_FALSE(source.ReadAll(&buffer, kBlockSize, block_reader, failing_stash_reader));
+}
+
+TEST(TransferListTest, Parse) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "2", // total blocks
+ "1", // max stashed entries
+ "1", // max stashed blocks
+ "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1 2,0,1",
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_TRUE(static_cast<bool>(transfer_list));
+ ASSERT_EQ(4, transfer_list.version());
+ ASSERT_EQ(2, transfer_list.total_blocks());
+ ASSERT_EQ(1, transfer_list.stash_max_entries());
+ ASSERT_EQ(1, transfer_list.stash_max_blocks());
+ ASSERT_EQ(2U, transfer_list.commands().size());
+ ASSERT_EQ(Command::Type::STASH, transfer_list.commands()[0].type());
+ ASSERT_EQ(Command::Type::MOVE, transfer_list.commands()[1].type());
+}
+
+TEST(TransferListTest, Parse_InvalidCommand) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "2", // total blocks
+ "1", // max stashed entries
+ "1", // max stashed blocks
+ "stash 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1",
+ "move 1d74d1a60332fd38cf9405f1bae67917888da6cb 2,0,1 1",
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_FALSE(static_cast<bool>(transfer_list));
+}
+
+TEST(TransferListTest, Parse_ZeroTotalBlocks) {
+ std::vector<std::string> input_lines{
+ "4", // version
+ "0", // total blocks
+ "0", // max stashed entries
+ "0", // max stashed blocks
+ };
+
+ std::string err;
+ TransferList transfer_list = TransferList::Parse(android::base::Join(input_lines, '\n'), &err);
+ ASSERT_TRUE(static_cast<bool>(transfer_list));
+ ASSERT_EQ(4, transfer_list.version());
+ ASSERT_EQ(0, transfer_list.total_blocks());
+ ASSERT_EQ(0, transfer_list.stash_max_entries());
+ ASSERT_EQ(0, transfer_list.stash_max_blocks());
+ ASSERT_TRUE(transfer_list.commands().empty());
+}
diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp
index 7f85d13ea..4dd111a70 100644
--- a/tests/unit/dirutil_test.cpp
+++ b/tests/unit/dirutil_test.cpp
@@ -20,9 +20,10 @@
#include <string>
-#include <android-base/test_utils.h>
+#include <android-base/file.h>
#include <gtest/gtest.h>
-#include <otautil/DirUtil.h>
+
+#include "otautil/dirutil.h"
TEST(DirUtilTest, create_invalid) {
// Requesting to create an empty dir is invalid.
diff --git a/tests/component/edify_test.cpp b/tests/unit/edify_test.cpp
index 61a1e6b64..8397bd38e 100644
--- a/tests/component/edify_test.cpp
+++ b/tests/unit/edify_test.cpp
@@ -21,30 +21,29 @@
#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());
- }
-
+static void expect(const std::string& expr_str, const char* expected) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ EXPECT_EQ(0, ParseString(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();
- }
+ protected:
+ void SetUp() {
+ RegisterBuiltins();
+ }
};
TEST_F(EdifyTest, parsing) {
@@ -146,25 +145,23 @@ TEST_F(EdifyTest, comparison) {
}
TEST_F(EdifyTest, big_string) {
- // big string
- expect(std::string(8192, 's').c_str(), std::string(8192, 's').c_str());
+ expect(std::string(8192, 's'), 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);
+ const char* script1 = "unknown_function()";
+ std::unique_ptr<Expr> expr;
+ int error_count = 0;
+ EXPECT_EQ(1, ParseString(script1, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script2 = "abc; unknown_function()";
+ error_count = 0;
+ EXPECT_EQ(1, ParseString(script2, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
+
+ const char* script3 = "unknown_function1() || yes";
+ error_count = 0;
+ EXPECT_EQ(1, ParseString(script3, &expr, &error_count));
+ EXPECT_EQ(1, error_count);
}
diff --git a/tests/unit/fuse_provider_test.cpp b/tests/unit/fuse_provider_test.cpp
new file mode 100644
index 000000000..c5995dd7d
--- /dev/null
+++ b/tests/unit/fuse_provider_test.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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 <stdint.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+
+#include "fuse_provider.h"
+#include "fuse_sideload.h"
+#include "install/install.h"
+
+TEST(FuseBlockMapTest, CreateFromBlockMap_smoke) {
+ TemporaryFile fake_block_device;
+ std::vector<std::string> lines = {
+ fake_block_device.path, "10000 4096", "3", "10 11", "20 21", "22 23",
+ };
+
+ TemporaryFile temp_file;
+ android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+ ASSERT_TRUE(block_map_data);
+ ASSERT_EQ(10000, block_map_data->file_size());
+ ASSERT_EQ(4096, block_map_data->fuse_block_size());
+ ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 21 }, { 22, 23 } }), block_map_data->ranges());
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_smoke) {
+ std::string content;
+ content.reserve(40960);
+ for (char c = 0; c < 10; c++) {
+ content += std::string(4096, c);
+ }
+ TemporaryFile fake_block_device;
+ ASSERT_TRUE(android::base::WriteStringToFile(content, fake_block_device.path));
+
+ std::vector<std::string> lines = {
+ fake_block_device.path,
+ "20000 4096",
+ "1",
+ "0 5",
+ };
+ TemporaryFile temp_file;
+ android::base::WriteStringToFile(android::base::Join(lines, '\n'), temp_file.path);
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(temp_file.path, 4096);
+
+ std::vector<uint8_t> result(2000);
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 2000, 1));
+ ASSERT_EQ(std::vector<uint8_t>(content.begin() + 4096, content.begin() + 6096), result);
+
+ result.resize(20000);
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 0));
+ ASSERT_EQ(std::vector<uint8_t>(content.begin(), content.begin() + 20000), result);
+}
+
+TEST(FuseBlockMapTest, ReadBlockAlignedData_large_fuse_block) {
+ std::string content;
+ for (char c = 0; c < 10; c++) {
+ content += std::string(4096, c);
+ }
+
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+
+ std::vector<std::string> lines = {
+ temp_file.path, "36384 4096", "2", "0 5", "6 10",
+ };
+ TemporaryFile block_map;
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(lines, '\n'), block_map.path));
+
+ auto block_map_data = FuseBlockDataProvider::CreateFromBlockMap(block_map.path, 16384);
+ ASSERT_TRUE(block_map_data);
+
+ std::vector<uint8_t> result(20000);
+ // Out of bound read
+ ASSERT_FALSE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 2));
+ ASSERT_TRUE(block_map_data->ReadBlockAlignedData(result.data(), 20000, 1));
+ // expected source block contains: 4, 6-9
+ std::string expected = content.substr(16384, 4096) + content.substr(24576, 15904);
+ ASSERT_EQ(std::vector<uint8_t>(expected.begin(), expected.end()), result);
+}
diff --git a/tests/component/sideload_test.cpp b/tests/unit/fuse_sideload_test.cpp
index b7109fcc2..6add99f41 100644
--- a/tests/component/sideload_test.cpp
+++ b/tests/unit/fuse_sideload_test.cpp
@@ -16,29 +16,42 @@
#include <unistd.h>
+#include <memory>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
+#include "fuse_provider.h"
#include "fuse_sideload.h"
TEST(SideloadTest, fuse_device) {
ASSERT_EQ(0, access("/dev/fuse", R_OK | W_OK));
}
+class FuseTestDataProvider : public FuseDataProvider {
+ public:
+ FuseTestDataProvider(uint64_t file_size, uint32_t block_size)
+ : FuseDataProvider(file_size, block_size) {}
+
+ private:
+ bool ReadBlockAlignedData(uint8_t*, uint32_t, uint32_t) const override {
+ return true;
+ }
+};
+
TEST(SideloadTest, run_fuse_sideload_wrong_parameters) {
- provider_vtab vtab;
- vtab.close = [](void) {};
+ auto provider_small_block = std::make_unique<FuseTestDataProvider>(4096, 4095);
+ ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_small_block)));
- ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, 4095));
- ASSERT_EQ(-1, run_fuse_sideload(vtab, 4096, (1 << 22) + 1));
+ auto provider_large_block = std::make_unique<FuseTestDataProvider>(4096, (1 << 22) + 1);
+ ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_large_block)));
- // Too many blocks.
- ASSERT_EQ(-1, run_fuse_sideload(vtab, ((1 << 18) + 1) * 4096, 4096));
+ auto provider_too_many_blocks =
+ std::make_unique<FuseTestDataProvider>(((1 << 18) + 1) * 4096, 4096);
+ ASSERT_EQ(-1, run_fuse_sideload(std::move(provider_too_many_blocks)));
}
TEST(SideloadTest, run_fuse_sideload) {
@@ -51,18 +64,15 @@ TEST(SideloadTest, run_fuse_sideload) {
const std::string content = android::base::Join(blocks, "");
ASSERT_EQ(16384U, content.size());
- provider_vtab vtab;
- vtab.close = [](void) {};
- vtab.read_block = [&blocks](uint32_t block, uint8_t* buffer, uint32_t fetch_size) {
- if (block >= 4) return -1;
- blocks[block].copy(reinterpret_cast<char*>(buffer), fetch_size);
- return 0;
- };
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path));
+ auto provider = std::make_unique<FuseFileDataProvider>(temp_file.path, 4096);
+ ASSERT_TRUE(provider->Valid());
TemporaryDir mount_point;
pid_t pid = fork();
if (pid == 0) {
- ASSERT_EQ(0, run_fuse_sideload(vtab, 16384, 4096, mount_point.path));
+ ASSERT_EQ(0, run_fuse_sideload(std::move(provider), mount_point.path));
_exit(EXIT_SUCCESS);
}
diff --git a/tests/component/imgdiff_test.cpp b/tests/unit/imgdiff_test.cpp
index 6c23def01..e76ccbdfb 100644
--- a/tests/component/imgdiff_test.cpp
+++ b/tests/unit/imgdiff_test.cpp
@@ -25,7 +25,6 @@
#include <android-base/memory.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/test_utils.h>
#include <applypatch/imgdiff.h>
#include <applypatch/imgdiff_image.h>
#include <applypatch/imgpatch.h>
@@ -197,12 +196,17 @@ TEST(ImgdiffTest, zip_mode_smoke_store) {
}
TEST(ImgdiffTest, zip_mode_smoke_compressed) {
+ // Generate 1 block of random data.
+ std::string random_data;
+ random_data.reserve(4096);
+ generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; });
+
// Construct src and tgt zip files.
TemporaryFile src_file;
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string src_content("abcdefg");
+ const std::string src_content = random_data;
ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
ASSERT_EQ(0, src_writer.FinishEntry());
ASSERT_EQ(0, src_writer.Finish());
@@ -212,7 +216,7 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string tgt_content("abcdefgxyz");
+ const std::string tgt_content = random_data + "extra contents";
ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
ASSERT_EQ(0, tgt_writer.FinishEntry());
ASSERT_EQ(0, tgt_writer.Finish());
@@ -245,13 +249,57 @@ TEST(ImgdiffTest, zip_mode_smoke_compressed) {
verify_patched_image(src, patch, tgt);
}
+TEST(ImgdiffTest, zip_mode_empty_target) {
+ TemporaryFile src_file;
+ FILE* src_file_ptr = fdopen(src_file.release(), "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));
+
+ // Construct a empty entry in the target zip.
+ TemporaryFile tgt_file;
+ FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
+ ZipWriter tgt_writer(tgt_file_ptr);
+ ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
+ const std::string tgt_content;
+ ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
+ ASSERT_EQ(0, tgt_writer.FinishEntry());
+ ASSERT_EQ(0, tgt_writer.Finish());
+
+ // 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));
+
+ verify_patched_image(src, patch, tgt);
+}
+
TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
+ // Generate 1 block of random data.
+ std::string random_data;
+ random_data.reserve(4096);
+ generate_n(back_inserter(random_data), 4096, []() { return rand() % 256; });
+
// Construct src and tgt zip files.
TemporaryFile src_file;
FILE* src_file_ptr = fdopen(src_file.release(), "wb");
ZipWriter src_writer(src_file_ptr);
ASSERT_EQ(0, src_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string src_content("abcdefg");
+ const std::string src_content = random_data;
ASSERT_EQ(0, src_writer.WriteBytes(src_content.data(), src_content.size()));
ASSERT_EQ(0, src_writer.FinishEntry());
ASSERT_EQ(0, src_writer.Finish());
@@ -261,7 +309,7 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
FILE* tgt_file_ptr = fdopen(tgt_file.release(), "wb");
ZipWriter tgt_writer(tgt_file_ptr);
ASSERT_EQ(0, tgt_writer.StartEntry("file1.txt", ZipWriter::kCompress));
- const std::string tgt_content("abcdefgxyz");
+ const std::string tgt_content = random_data + "abcdefg";
ASSERT_EQ(0, tgt_writer.WriteBytes(tgt_content.data(), tgt_content.size()));
ASSERT_EQ(0, tgt_writer.FinishEntry());
ASSERT_EQ(0, tgt_writer.Finish());
@@ -298,23 +346,19 @@ TEST(ImgdiffTest, zip_mode_smoke_trailer_zeros) {
}
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());
+ std::string gzipped_source_path = from_testdata_base("gzipped_source");
+ std::string gzipped_source;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source));
+
+ const std::string src = "abcdefg" + gzipped_source;
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());
+ std::string gzipped_target_path = from_testdata_base("gzipped_target");
+ std::string gzipped_target;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target));
+ const std::string tgt = "abcdefgxyz" + gzipped_target;
+
TemporaryFile tgt_file;
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
@@ -404,23 +448,21 @@ TEST(ImgdiffTest, image_mode_different_num_chunks) {
}
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());
+ // src: "abcdefg" + gzipped_source.
+ std::string gzipped_source_path = from_testdata_base("gzipped_source");
+ std::string gzipped_source;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_source_path, &gzipped_source));
+
+ const std::string src = "abcdefg" + gzipped_source;
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());
+ // tgt: gzipped_target + "abcdefgxyz".
+ std::string gzipped_target_path = from_testdata_base("gzipped_target");
+ std::string gzipped_target;
+ ASSERT_TRUE(android::base::ReadFileToString(gzipped_target_path, &gzipped_target));
+
+ const std::string tgt = gzipped_target + "abcdefgxyz";
TemporaryFile tgt_file;
ASSERT_TRUE(android::base::WriteStringToFile(tgt, tgt_file.path));
diff --git a/tests/unit/install_test.cpp b/tests/unit/install_test.cpp
new file mode 100644
index 000000000..4ec409908
--- /dev/null
+++ b/tests/unit/install_test.cpp
@@ -0,0 +1,597 @@
+/*
+ * 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <random>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <vintf/VintfObjectRecovery.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "install/install.h"
+#include "install/wipe_device.h"
+#include "otautil/paths.h"
+#include "private/setup_commands.h"
+
+static void BuildZipArchive(const std::map<std::string, std::string>& file_map, int fd,
+ int compression_type) {
+ FILE* zip_file = fdopen(fd, "w");
+ ZipWriter writer(zip_file);
+ for (const auto& [name, content] : file_map) {
+ ASSERT_EQ(0, writer.StartEntry(name.c_str(), compression_type));
+ ASSERT_EQ(0, writer.WriteBytes(content.data(), content.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ }
+ ASSERT_EQ(0, writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file));
+}
+
+TEST(InstallTest, verify_package_compatibility_no_entry) {
+ TemporaryFile temp_file;
+ // The archive must have something to be opened correctly.
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
+
+ // 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;
+ BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored);
+
+ // Empty compatibility zip entry.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ASSERT_FALSE(verify_package_compatibility(zip));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, read_metadata_from_package_smoke) {
+ TemporaryFile temp_file;
+ const std::string content("abc=defg");
+ BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file.release(),
+ kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ("defg", metadata["abc"]);
+ CloseArchive(zip);
+
+ TemporaryFile temp_file2;
+ BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file2.release(),
+ kCompressDeflated);
+
+ ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip));
+ metadata.clear();
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ("defg", metadata["abc"]);
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, read_metadata_from_package_no_entry) {
+ TemporaryFile temp_file;
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ std::map<std::string, std::string> metadata;
+ ASSERT_FALSE(ReadMetadataFromPackage(zip, &metadata));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, read_wipe_ab_partition_list) {
+ std::vector<std::string> partition_list = {
+ "/dev/block/bootdevice/by-name/system_a", "/dev/block/bootdevice/by-name/system_b",
+ "/dev/block/bootdevice/by-name/vendor_a", "/dev/block/bootdevice/by-name/vendor_b",
+ "/dev/block/bootdevice/by-name/userdata", "# Wipe the boot partitions last",
+ "/dev/block/bootdevice/by-name/boot_a", "/dev/block/bootdevice/by-name/boot_b",
+ };
+ TemporaryFile temp_file;
+ BuildZipArchive({ { "recovery.wipe", android::base::Join(partition_list, '\n') } },
+ temp_file.release(), kCompressDeflated);
+ std::string wipe_package;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file.path, &wipe_package));
+
+ auto package = Package::CreateMemoryPackage(
+ std::vector<uint8_t>(wipe_package.begin(), wipe_package.end()), nullptr);
+
+ auto read_partition_list = GetWipePartitionList(package.get());
+ std::vector<std::string> expected = {
+ "/dev/block/bootdevice/by-name/system_a", "/dev/block/bootdevice/by-name/system_b",
+ "/dev/block/bootdevice/by-name/vendor_a", "/dev/block/bootdevice/by-name/vendor_b",
+ "/dev/block/bootdevice/by-name/userdata", "/dev/block/bootdevice/by-name/boot_a",
+ "/dev/block/bootdevice/by-name/boot_b",
+ };
+ ASSERT_EQ(expected, read_partition_list);
+}
+
+TEST(InstallTest, verify_package_compatibility_with_libvintf_malformed_xml) {
+ TemporaryFile compatibility_zip_file;
+ std::string malformed_xml = "malformed";
+ BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(),
+ kCompressDeflated);
+
+ TemporaryFile temp_file;
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+ kCompressStored);
+
+ 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;
+ BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } },
+ compatibility_zip_file.release(), kCompressDeflated);
+
+ TemporaryFile temp_file;
+ std::string compatibility_zip_content;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(compatibility_zip_file.path, &compatibility_zip_content));
+ BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(),
+ kCompressStored);
+
+ 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, SetUpNonAbUpdateCommands) {
+ TemporaryFile temp_file;
+ static constexpr const char* UPDATE_BINARY_NAME = "META-INF/com/google/android/update-binary";
+ BuildZipArchive({ { UPDATE_BINARY_NAME, "" } }, temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ TemporaryDir td;
+ std::string binary_path = std::string(td.path) + "/update_binary";
+ Paths::Get().set_temporary_update_binary(binary_path);
+ std::vector<std::string> cmd;
+ ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ ASSERT_EQ(4U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
+ ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+ ASSERT_EQ(package, cmd[3]);
+ struct stat sb;
+ ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+ ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+ // With non-zero retry count. update_binary will be removed automatically.
+ cmd.clear();
+ ASSERT_TRUE(SetUpNonAbUpdateCommands(package, zip, 2, status_fd, &cmd));
+ ASSERT_EQ(5U, cmd.size());
+ ASSERT_EQ(binary_path, cmd[0]);
+ ASSERT_EQ("3", cmd[1]); // RECOVERY_API_VERSION
+ ASSERT_EQ(std::to_string(status_fd), cmd[2]);
+ ASSERT_EQ(package, cmd[3]);
+ ASSERT_EQ("retry", cmd[4]);
+ sb = {};
+ ASSERT_EQ(0, stat(binary_path.c_str(), &sb));
+ ASSERT_EQ(static_cast<mode_t>(0755), sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) {
+ TemporaryFile temp_file;
+ // The archive must have something to be opened correctly.
+ BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored);
+
+ // Missing update binary.
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ TemporaryDir td;
+ Paths::Get().set_temporary_update_binary(std::string(td.path) + "/update_binary");
+ std::vector<std::string> cmd;
+ ASSERT_FALSE(SetUpNonAbUpdateCommands(package, zip, 0, status_fd, &cmd));
+ CloseArchive(zip);
+}
+
+static void VerifyAbUpdateCommands(const std::string& serialno, bool success = true) {
+ TemporaryFile temp_file;
+
+ const std::string properties = "some_properties";
+ 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::vector<std::string> meta{ "ota-type=AB", "pre-device=" + device,
+ "post-timestamp=" + timestamp };
+ if (!serialno.empty()) {
+ meta.push_back("serialno=" + serialno);
+ }
+ std::string metadata_string = android::base::Join(meta, "\n");
+
+ BuildZipArchive({ { "payload.bin", "" },
+ { "payload_properties.txt", properties },
+ { "META-INF/com/android/metadata", metadata_string } },
+ temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ ZipEntry payload_entry;
+ ASSERT_EQ(0, FindEntry(zip, "payload.bin", &payload_entry));
+
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ if (success) {
+ ASSERT_TRUE(CheckPackageMetadata(metadata, OtaType::AB));
+
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_TRUE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+ ASSERT_EQ(5U, cmd.size());
+ ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]);
+ ASSERT_EQ("--payload=file://" + package, cmd[1]);
+ ASSERT_EQ("--offset=" + std::to_string(payload_entry.offset), cmd[2]);
+ ASSERT_EQ("--headers=" + properties, cmd[3]);
+ ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]);
+ } else {
+ ASSERT_FALSE(CheckPackageMetadata(metadata, OtaType::AB));
+ }
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, SetUpAbUpdateCommands) {
+ // Empty serialno will pass the verification.
+ VerifyAbUpdateCommands({});
+}
+
+TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) {
+ TemporaryFile temp_file;
+
+ 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");
+
+ BuildZipArchive(
+ {
+ { "payload.bin", "" },
+ { "META-INF/com/android/metadata", metadata },
+ },
+ temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+ int status_fd = 10;
+ std::string package = "/path/to/update.zip";
+ std::vector<std::string> cmd;
+ ASSERT_FALSE(SetUpAbUpdateCommands(package, zip, status_fd, &cmd));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) {
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+
+ // Single matching serialno will pass the verification.
+ VerifyAbUpdateCommands(serialno);
+
+ static constexpr char alphabet[] =
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ auto generator = []() { return alphabet[rand() % (sizeof(alphabet) - 1)]; };
+
+ // Generate 900 random serial numbers.
+ std::string random_serialno;
+ for (size_t i = 0; i < 900; i++) {
+ generate_n(back_inserter(random_serialno), serialno.size(), generator);
+ random_serialno.append("|");
+ }
+ // Random serialnos should fail the verification.
+ VerifyAbUpdateCommands(random_serialno, false);
+
+ std::string long_serialno = random_serialno + serialno + "|";
+ for (size_t i = 0; i < 99; i++) {
+ generate_n(back_inserter(long_serialno), serialno.size(), generator);
+ long_serialno.append("|");
+ }
+ // String with the matching serialno should pass the verification.
+ VerifyAbUpdateCommands(long_serialno);
+}
+
+static void TestCheckPackageMetadata(const std::string& metadata_string, OtaType ota_type,
+ bool exptected_result) {
+ TemporaryFile temp_file;
+ BuildZipArchive(
+ {
+ { "META-INF/com/android/metadata", metadata_string },
+ },
+ temp_file.release(), kCompressStored);
+
+ ZipArchiveHandle zip;
+ ASSERT_EQ(0, OpenArchive(temp_file.path, &zip));
+
+ std::map<std::string, std::string> metadata;
+ ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata));
+ ASSERT_EQ(exptected_result, CheckPackageMetadata(metadata, ota_type));
+ CloseArchive(zip);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ota_type) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // ota-type must be present
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "pre-device=" + device,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+
+ // Checks if ota-type matches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
+
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
+}
+
+TEST(InstallTest, CheckPackageMetadata_device_type) {
+ // device type can not be empty
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
+
+ // device type mismatches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=dummy_device_type",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
+}
+
+TEST(InstallTest, CheckPackageMetadata_serial_number_smoke) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // Serial number doesn't need to exist
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
+
+ // Serial number mismatches
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=dummy_serial",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
+
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + serialno,
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
+}
+
+TEST(InstallTest, CheckPackageMetadata_multiple_serial_number) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string serialno = android::base::GetProperty("ro.serialno", "");
+ ASSERT_NE("", serialno);
+
+ std::vector<std::string> serial_numbers;
+ // Creates a dummy serial number string.
+ for (char c = 'a'; c <= 'z'; c++) {
+ serial_numbers.emplace_back(serialno.size(), c);
+ }
+
+ // No matched serialno found.
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + android::base::Join(serial_numbers, '|'),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, false);
+
+ serial_numbers.emplace_back(serialno);
+ std::shuffle(serial_numbers.begin(), serial_numbers.end(), std::default_random_engine());
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=BRICK",
+ "pre-device=" + device,
+ "serialno=" + android::base::Join(serial_numbers, '|'),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::BRICK, true);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_build_version) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string build_version = android::base::GetProperty("ro.build.version.incremental", "");
+ ASSERT_NE("", build_version);
+
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build-incremental=" + build_version,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build-incremental=dummy_build",
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_fingerprint) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+ ASSERT_NE("", finger_print);
+
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build=" + finger_print,
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "pre-build=dummy_build_fingerprint",
+ "post-timestamp=" + std::to_string(std::numeric_limits<int64_t>::max()),
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+}
+
+TEST(InstallTest, CheckPackageMetadata_ab_post_timestamp) {
+ std::string device = android::base::GetProperty("ro.product.device", "");
+ ASSERT_NE("", device);
+
+ // post timestamp is required for upgrade.
+ std::string metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+
+ // post timestamp should be larger than the timestamp on device.
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+
+ // fingerprint is required for downgrade
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ "ota-downgrade=yes",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, false);
+
+ std::string finger_print = android::base::GetProperty("ro.build.fingerprint", "");
+ ASSERT_NE("", finger_print);
+
+ metadata = android::base::Join(
+ std::vector<std::string>{
+ "ota-type=AB",
+ "pre-device=" + device,
+ "post-timestamp=0",
+ "pre-build=" + finger_print,
+ "ota-downgrade=yes",
+ },
+ "\n");
+ TestCheckPackageMetadata(metadata, OtaType::AB, true);
+}
diff --git a/tests/unit/locale_test.cpp b/tests/unit/locale_test.cpp
index cdaba0e8b..c69434c12 100644
--- a/tests/unit/locale_test.cpp
+++ b/tests/unit/locale_test.cpp
@@ -27,7 +27,7 @@ TEST(LocaleTest, Misc) {
EXPECT_FALSE(matches_locale("en-GB", "en"));
EXPECT_FALSE(matches_locale("en-GB", "en-US"));
EXPECT_FALSE(matches_locale("en-US", ""));
- // Empty locale prefix in the PNG file will match the input locale.
- EXPECT_TRUE(matches_locale("", "en-US"));
+ // Empty locale prefix in the PNG file should not match the input locale.
+ EXPECT_FALSE(matches_locale("", "en-US"));
EXPECT_TRUE(matches_locale("sr-Latn", "sr-Latn-BA"));
}
diff --git a/tests/unit/minui_test.cpp b/tests/unit/minui_test.cpp
new file mode 100644
index 000000000..c7d7f7eef
--- /dev/null
+++ b/tests/unit/minui_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <limits>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "minui/minui.h"
+
+TEST(GRSurfaceTest, Create_aligned) {
+ auto surface = GRSurface::Create(9, 11, 9, 1);
+ ASSERT_TRUE(surface);
+ ASSERT_EQ(0, reinterpret_cast<uintptr_t>(surface->data()) % GRSurface::kSurfaceDataAlignment);
+ // data_size will be rounded up to the next multiple of GRSurface::kSurfaceDataAlignment.
+ ASSERT_EQ(0, surface->data_size() % GRSurface::kSurfaceDataAlignment);
+ ASSERT_GE(surface->data_size(), 11 * 9);
+}
+
+TEST(GRSurfaceTest, Create_invalid_inputs) {
+ ASSERT_FALSE(GRSurface::Create(9, 11, 0, 1));
+ ASSERT_FALSE(GRSurface::Create(9, 0, 9, 1));
+ ASSERT_FALSE(GRSurface::Create(0, 11, 9, 1));
+ ASSERT_FALSE(GRSurface::Create(9, 11, 9, 0));
+ ASSERT_FALSE(GRSurface::Create(9, 101, std::numeric_limits<size_t>::max() / 100, 1));
+}
+
+TEST(GRSurfaceTest, Clone) {
+ auto image = GRSurface::Create(50, 10, 50, 1);
+ ASSERT_GE(image->data_size(), 10 * 50);
+ for (auto i = 0; i < image->data_size(); i++) {
+ image->data()[i] = rand() % 128;
+ }
+ auto image_copy = image->Clone();
+ ASSERT_EQ(image->data_size(), image_copy->data_size());
+ ASSERT_EQ(std::vector(image->data(), image->data() + image->data_size()),
+ std::vector(image_copy->data(), image_copy->data() + image->data_size()));
+}
diff --git a/tests/unit/package_test.cpp b/tests/unit/package_test.cpp
new file mode 100644
index 000000000..5e31f7fa5
--- /dev/null
+++ b/tests/unit/package_test.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 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 <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <openssl/sha.h>
+#include <ziparchive/zip_writer.h>
+
+#include "common/test_constants.h"
+#include "install/package.h"
+
+class PackageTest : public ::testing::Test {
+ protected:
+ void SetUp() override;
+
+ // A list of package classes for test, including MemoryPackage and FilePackage.
+ std::vector<std::unique_ptr<Package>> packages_;
+
+ TemporaryFile temp_file_; // test package file.
+ std::string file_content_; // actual bytes of the package file.
+};
+
+void PackageTest::SetUp() {
+ std::vector<std::string> entries = { "file1.txt", "file2.txt", "dir1/file3.txt" };
+ FILE* file_ptr = fdopen(temp_file_.release(), "wb");
+ ZipWriter writer(file_ptr);
+ for (const auto& entry : entries) {
+ ASSERT_EQ(0, writer.StartEntry(entry.c_str(), ZipWriter::kCompress));
+ ASSERT_EQ(0, writer.WriteBytes(entry.c_str(), entry.size()));
+ ASSERT_EQ(0, writer.FinishEntry());
+ }
+ writer.Finish();
+ ASSERT_EQ(0, fclose(file_ptr));
+
+ ASSERT_TRUE(android::base::ReadFileToString(temp_file_.path, &file_content_));
+ auto memory_package = Package::CreateMemoryPackage(temp_file_.path, nullptr);
+ ASSERT_TRUE(memory_package);
+ packages_.emplace_back(std::move(memory_package));
+
+ auto file_package = Package::CreateFilePackage(temp_file_.path, nullptr);
+ ASSERT_TRUE(file_package);
+ packages_.emplace_back(std::move(file_package));
+}
+
+TEST_F(PackageTest, ReadFullyAtOffset_success) {
+ for (const auto& package : packages_) {
+ std::vector<uint8_t> buffer(file_content_.size());
+ ASSERT_TRUE(package->ReadFullyAtOffset(buffer.data(), file_content_.size(), 0));
+ ASSERT_EQ(file_content_, std::string(buffer.begin(), buffer.end()));
+
+ ASSERT_TRUE(package->ReadFullyAtOffset(buffer.data(), file_content_.size() - 10, 10));
+ ASSERT_EQ(file_content_.substr(10), std::string(buffer.begin(), buffer.end() - 10));
+ }
+}
+
+TEST_F(PackageTest, ReadFullyAtOffset_failure) {
+ for (const auto& package : packages_) {
+ std::vector<uint8_t> buffer(file_content_.size());
+ // Out of bound read.
+ ASSERT_FALSE(package->ReadFullyAtOffset(buffer.data(), file_content_.size(), 10));
+ }
+}
+
+TEST_F(PackageTest, UpdateHashAtOffset_sha1_hash) {
+ // Check that the hash matches for first half of the file.
+ uint64_t hash_size = file_content_.size() / 2;
+ std::vector<uint8_t> expected_sha(SHA_DIGEST_LENGTH);
+ SHA1(reinterpret_cast<uint8_t*>(file_content_.data()), hash_size, expected_sha.data());
+
+ for (const auto& package : packages_) {
+ SHA_CTX ctx;
+ SHA1_Init(&ctx);
+ std::vector<HasherUpdateCallback> hashers{ std::bind(&SHA1_Update, &ctx, std::placeholders::_1,
+ std::placeholders::_2) };
+ package->UpdateHashAtOffset(hashers, 0, hash_size);
+
+ std::vector<uint8_t> calculated_sha(SHA_DIGEST_LENGTH);
+ SHA1_Final(calculated_sha.data(), &ctx);
+ ASSERT_EQ(expected_sha, calculated_sha);
+ }
+}
+
+TEST_F(PackageTest, GetZipArchiveHandle_extract_entry) {
+ for (const auto& package : packages_) {
+ ZipArchiveHandle zip = package->GetZipArchiveHandle();
+ ASSERT_TRUE(zip);
+
+ // Check that we can extract one zip entry.
+ std::string_view entry_name = "dir1/file3.txt";
+ ZipEntry entry;
+ ASSERT_EQ(0, FindEntry(zip, entry_name, &entry));
+
+ std::vector<uint8_t> extracted(entry_name.size());
+ ASSERT_EQ(0, ExtractToMemory(zip, &entry, extracted.data(), extracted.size()));
+ ASSERT_EQ(entry_name, std::string(extracted.begin(), extracted.end()));
+ }
+}
diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp
new file mode 100644
index 000000000..72169a0c6
--- /dev/null
+++ b/tests/unit/parse_install_logs_test.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+
+#include "otautil/parse_install_logs.h"
+
+TEST(ParseInstallLogsTest, EmptyFile) {
+ TemporaryFile last_install;
+
+ auto metrics = ParseLastInstall(last_install.path);
+ ASSERT_TRUE(metrics.empty());
+}
+
+TEST(ParseInstallLogsTest, SideloadSmoke) {
+ TemporaryFile last_install;
+ ASSERT_TRUE(android::base::WriteStringToFile("/cache/recovery/ota.zip\n0\n", last_install.path));
+ auto metrics = ParseLastInstall(last_install.path);
+ ASSERT_EQ(metrics.end(), metrics.find("ota_sideload"));
+
+ ASSERT_TRUE(android::base::WriteStringToFile("/sideload/package.zip\n0\n", last_install.path));
+ metrics = ParseLastInstall(last_install.path);
+ ASSERT_NE(metrics.end(), metrics.find("ota_sideload"));
+}
+
+TEST(ParseInstallLogsTest, ParseRecoveryUpdateMetrics) {
+ std::vector<std::string> lines = {
+ "/sideload/package.zip",
+ "0",
+ "time_total: 300",
+ "uncrypt_time: 40",
+ "source_build: 4973410",
+ "bytes_written_system: " + std::to_string(1200 * 1024 * 1024),
+ "bytes_stashed_system: " + std::to_string(300 * 1024 * 1024),
+ "bytes_written_vendor: " + std::to_string(40 * 1024 * 1024),
+ "bytes_stashed_vendor: " + std::to_string(50 * 1024 * 1024),
+ "temperature_start: 37000",
+ "temperature_end: 38000",
+ "temperature_max: 39000",
+ "error: 22",
+ "cause: 55",
+ };
+
+ auto metrics = ParseRecoveryUpdateMetrics(lines);
+
+ std::map<std::string, int64_t> expected_result = {
+ { "ota_time_total", 300 }, { "ota_uncrypt_time", 40 },
+ { "ota_source_version", 4973410 }, { "ota_written_in_MiBs", 1240 },
+ { "ota_stashed_in_MiBs", 350 }, { "ota_temperature_start", 37000 },
+ { "ota_temperature_end", 38000 }, { "ota_temperature_max", 39000 },
+ { "ota_non_ab_error_code", 22 }, { "ota_non_ab_cause_code", 55 },
+ };
+
+ ASSERT_EQ(expected_result, metrics);
+}
diff --git a/tests/unit/rangeset_test.cpp b/tests/unit/rangeset_test.cpp
index 7ae193e18..699f933a0 100644
--- a/tests/unit/rangeset_test.cpp
+++ b/tests/unit/rangeset_test.cpp
@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <limits>
+#include <optional>
#include <vector>
#include <gtest/gtest.h>
@@ -209,6 +210,7 @@ TEST(RangeSetTest, GetBlockNumber) {
ASSERT_EQ(static_cast<size_t>(6), rs.GetBlockNumber(5));
ASSERT_EQ(static_cast<size_t>(9), rs.GetBlockNumber(8));
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
// Out of bound.
ASSERT_EXIT(rs.GetBlockNumber(9), ::testing::KilledBySignal(SIGABRT), "");
}
@@ -247,6 +249,29 @@ TEST(RangeSetTest, ToString) {
ASSERT_EQ("6,1,3,4,6,15,22", RangeSet::Parse("6,1,3,4,6,15,22").ToString());
}
+TEST(RangeSetTest, GetSubRanges_invalid) {
+ RangeSet range0({ { 1, 11 }, { 20, 30 } });
+ ASSERT_FALSE(range0.GetSubRanges(0, 21)); // too many blocks
+ ASSERT_FALSE(range0.GetSubRanges(21, 1)); // start block OOB
+}
+
+TEST(RangeSetTest, GetSubRanges_empty) {
+ RangeSet range0({ { 1, 11 }, { 20, 30 } });
+ ASSERT_EQ(RangeSet{}, range0.GetSubRanges(1, 0)); // empty num_of_blocks
+}
+
+TEST(RangeSetTest, GetSubRanges_smoke) {
+ RangeSet range0({ { 10, 11 } });
+ ASSERT_EQ(RangeSet({ { 10, 11 } }), range0.GetSubRanges(0, 1));
+
+ RangeSet range1({ { 10, 11 }, { 20, 21 }, { 30, 31 } });
+ ASSERT_EQ(range1, range1.GetSubRanges(0, 3));
+ ASSERT_EQ(RangeSet({ { 20, 21 } }), range1.GetSubRanges(1, 1));
+
+ RangeSet range2({ { 1, 11 }, { 20, 25 }, { 30, 35 } });
+ ASSERT_EQ(RangeSet({ { 10, 11 }, { 20, 25 }, { 30, 31 } }), range2.GetSubRanges(9, 7));
+}
+
TEST(SortedRangeSetTest, Insert) {
SortedRangeSet rs({ { 2, 3 }, { 4, 6 }, { 8, 14 } });
rs.Insert({ 1, 2 });
@@ -284,6 +309,8 @@ TEST(SortedRangeSetTest, file_range) {
ASSERT_EQ(static_cast<size_t>(10), rs.GetOffsetInRangeSet(4106));
ASSERT_EQ(static_cast<size_t>(40970), rs.GetOffsetInRangeSet(4096 * 16 + 10));
+
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
// block#10 not in range.
ASSERT_EXIT(rs.GetOffsetInRangeSet(40970), ::testing::KilledBySignal(SIGABRT), "");
}
diff --git a/tests/unit/resources_test.cpp b/tests/unit/resources_test.cpp
new file mode 100644
index 000000000..302744308
--- /dev/null
+++ b/tests/unit/resources_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
+#include <png.h>
+
+#include "common/test_constants.h"
+#include "minui/minui.h"
+#include "private/resources.h"
+
+static const std::string kLocale = "zu";
+
+static const std::vector<std::string> kResourceImagesDirs{
+ "res-mdpi/images/", "res-hdpi/images/", "res-xhdpi/images/",
+ "res-xxhdpi/images/", "res-xxxhdpi/images/",
+};
+
+static int png_filter(const dirent* de) {
+ if (de->d_type != DT_REG || !android::base::EndsWith(de->d_name, "_text.png")) {
+ return 0;
+ }
+ return 1;
+}
+
+// Finds out all the PNG files to test, which stay under the same dir with the executabl..
+static std::vector<std::string> add_files() {
+ std::vector<std::string> files;
+ for (const std::string& images_dir : kResourceImagesDirs) {
+ static std::string exec_dir = android::base::GetExecutableDirectory();
+ std::string dir_path = exec_dir + "/" + images_dir;
+ dirent** namelist;
+ int n = scandir(dir_path.c_str(), &namelist, png_filter, alphasort);
+ if (n == -1) {
+ printf("Failed to scandir %s: %s\n", dir_path.c_str(), strerror(errno));
+ continue;
+ }
+ if (n == 0) {
+ printf("No file is added for test in %s\n", dir_path.c_str());
+ }
+
+ while (n--) {
+ std::string file_path = dir_path + namelist[n]->d_name;
+ files.push_back(file_path);
+ free(namelist[n]);
+ }
+ free(namelist);
+ }
+ return files;
+}
+
+TEST(ResourcesTest, res_create_multi_display_surface) {
+ GRSurface** frames;
+ int frame_count;
+ int fps;
+ ASSERT_EQ(0, res_create_multi_display_surface(from_testdata_base("battery_scale.png").c_str(),
+ &frame_count, &fps, &frames));
+ ASSERT_EQ(6, frame_count);
+ ASSERT_EQ(20, fps);
+
+ for (auto i = 0; i < frame_count; i++) {
+ free(frames[i]);
+ }
+ free(frames);
+}
+
+class ResourcesTest : public testing::TestWithParam<std::string> {
+ public:
+ static std::vector<std::string> png_list;
+
+ protected:
+ void SetUp() override {
+ png_ = std::make_unique<PngHandler>(GetParam());
+ ASSERT_TRUE(png_);
+
+ ASSERT_EQ(PNG_COLOR_TYPE_GRAY, png_->color_type()) << "Recovery expects grayscale PNG file.";
+ ASSERT_LT(static_cast<png_uint_32>(5), png_->width());
+ ASSERT_LT(static_cast<png_uint_32>(0), png_->height());
+ ASSERT_EQ(1, png_->channels()) << "Recovery background text images expects 1-channel PNG file.";
+ }
+
+ std::unique_ptr<PngHandler> png_{ nullptr };
+};
+
+// Parses a png file and tests if it's qualified for the background text image under recovery.
+TEST_P(ResourcesTest, ValidateLocale) {
+ std::vector<unsigned char> row(png_->width());
+ for (png_uint_32 y = 0; y < png_->height(); ++y) {
+ png_read_row(png_->png_ptr(), row.data(), nullptr);
+ int w = (row[1] << 8) | row[0];
+ int h = (row[3] << 8) | row[2];
+ int len = row[4];
+ EXPECT_LT(0, w);
+ EXPECT_LT(0, h);
+ EXPECT_LT(0, len) << "Locale string should be non-empty.";
+ EXPECT_NE(0, row[5]) << "Locale string is missing.";
+
+ ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
+ char* loc = reinterpret_cast<char*>(&row[5]);
+ if (matches_locale(loc, kLocale.c_str())) {
+ EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
+ break;
+ }
+ for (int i = 0; i < h; ++i, ++y) {
+ png_read_row(png_->png_ptr(), row.data(), nullptr);
+ }
+ }
+}
+
+std::vector<std::string> ResourcesTest::png_list = add_files();
+
+INSTANTIATE_TEST_CASE_P(BackgroundTextValidation, ResourcesTest,
+ ::testing::ValuesIn(ResourcesTest::png_list.cbegin(),
+ ResourcesTest::png_list.cend()));
diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp
new file mode 100644
index 000000000..883dfbde2
--- /dev/null
+++ b/tests/unit/screen_ui_test.cpp
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stddef.h>
+#include <stdio.h>
+
+#include <functional>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+#include <gtest/gtest_prod.h>
+
+#include "common/test_constants.h"
+#include "minui/minui.h"
+#include "otautil/paths.h"
+#include "private/resources.h"
+#include "recovery_ui/device.h"
+#include "recovery_ui/screen_ui.h"
+
+static const std::vector<std::string> HEADERS{ "header" };
+static const std::vector<std::string> ITEMS{ "item1", "item2", "item3", "item4", "1234567890" };
+
+// TODO(xunchang) check if some draw functions are called when drawing menus.
+class MockDrawFunctions : public DrawInterface {
+ void SetColor(UIElement /* element */) const override {}
+ void DrawHighlightBar(int /* x */, int /* y */, int /* width */,
+ int /* height */) const override {}
+ int DrawHorizontalRule(int /* y */) const override {
+ return 0;
+ }
+ int DrawTextLine(int /* x */, int /* y */, const std::string& /* line */,
+ bool /* bold */) const override {
+ return 0;
+ }
+ void DrawSurface(const GRSurface* /* surface */, int /* sx */, int /* sy */, int /* w */,
+ int /* h */, int /* dx */, int /* dy */) const override {}
+ void DrawFill(int /* x */, int /* y */, int /* w */, int /* h */) const override {}
+ void DrawTextIcon(int /* x */, int /* y */, const GRSurface* /* surface */) const override {}
+ int DrawTextLines(int /* x */, int /* y */,
+ const std::vector<std::string>& /* lines */) const override {
+ return 0;
+ }
+ int DrawWrappedTextLines(int /* x */, int /* y */,
+ const std::vector<std::string>& /* lines */) const override {
+ return 0;
+ }
+};
+
+class ScreenUITest : public testing::Test {
+ protected:
+ MockDrawFunctions draw_funcs_;
+};
+
+TEST_F(ScreenUITest, StartPhoneMenuSmoke) {
+ TextMenu menu(false, 10, 20, HEADERS, ITEMS, 0, 20, draw_funcs_);
+ ASSERT_FALSE(menu.scrollable());
+ ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0, menu.selection());
+}
+
+TEST_F(ScreenUITest, StartWearMenuSmoke) {
+ TextMenu menu(true, 10, 8, HEADERS, ITEMS, 1, 20, draw_funcs_);
+ ASSERT_TRUE(menu.scrollable());
+ ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount() - 1; i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+ // Test of the last item is truncated
+ ASSERT_EQ("12345678", menu.TextItem(4));
+ ASSERT_EQ(1, menu.selection());
+}
+
+TEST_F(ScreenUITest, StartPhoneMenuItemsOverflow) {
+ TextMenu menu(false, 1, 20, HEADERS, ITEMS, 0, 20, draw_funcs_);
+ ASSERT_FALSE(menu.scrollable());
+ ASSERT_EQ(1u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_FALSE(menu.ItemsOverflow(&message));
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(1u, menu.MenuEnd());
+}
+
+TEST_F(ScreenUITest, StartWearMenuItemsOverflow) {
+ TextMenu menu(true, 1, 20, HEADERS, ITEMS, 0, 20, draw_funcs_);
+ ASSERT_TRUE(menu.scrollable());
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ std::string message;
+ ASSERT_TRUE(menu.ItemsOverflow(&message));
+ ASSERT_EQ("Current item: 1/5", message);
+
+ for (size_t i = 0; i < menu.ItemsCount(); i++) {
+ ASSERT_EQ(ITEMS[i], menu.TextItem(i));
+ }
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(1u, menu.MenuEnd());
+}
+
+TEST_F(ScreenUITest, PhoneMenuSelectSmoke) {
+ int sel = 0;
+ TextMenu menu(false, 10, 20, HEADERS, ITEMS, sel, 20, draw_funcs_);
+ // Mimic down button 10 times (2 * items size)
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ // Wraps the selection for unscrollable menu when it reaches the boundary.
+ int expected = (i + 1) % 5;
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+
+ // Mimic up button 10 times
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ int expected = (9 - i) % 5;
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+}
+
+TEST_F(ScreenUITest, WearMenuSelectSmoke) {
+ int sel = 0;
+ TextMenu menu(true, 10, 20, HEADERS, ITEMS, sel, 20, draw_funcs_);
+ // Mimic pressing down button 10 times (2 * items size)
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ // Stops the selection at the boundary if the menu is scrollable.
+ int expected = std::min(i + 1, 4);
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+
+ // Mimic pressing up button 10 times
+ for (int i = 0; i < 10; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(sel, menu.selection());
+
+ int expected = std::max(3 - i, 0);
+ ASSERT_EQ(expected, menu.selection());
+
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+ }
+}
+
+TEST_F(ScreenUITest, WearMenuSelectItemsOverflow) {
+ int sel = 1;
+ TextMenu menu(true, 3, 20, HEADERS, ITEMS, sel, 20, draw_funcs_);
+ ASSERT_EQ(5u, menu.ItemsCount());
+
+ // Scroll the menu to the end, and check the start & end of menu.
+ for (int i = 0; i < 3; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ(i + 2, sel);
+ ASSERT_EQ(static_cast<size_t>(i), menu.MenuStart());
+ ASSERT_EQ(static_cast<size_t>(i + 3), menu.MenuEnd());
+ }
+
+ // Press down button one more time won't change the MenuStart() and MenuEnd().
+ sel = menu.Select(++sel);
+ ASSERT_EQ(4, sel);
+ ASSERT_EQ(2u, menu.MenuStart());
+ ASSERT_EQ(5u, menu.MenuEnd());
+
+ // Scroll the menu to the top.
+ // The expected menu sel, start & ends are:
+ // sel 3, start 2, end 5
+ // sel 2, start 2, end 5
+ // sel 1, start 1, end 4
+ // sel 0, start 0, end 3
+ for (int i = 0; i < 4; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(3 - i, sel);
+ ASSERT_EQ(static_cast<size_t>(std::min(3 - i, 2)), menu.MenuStart());
+ ASSERT_EQ(static_cast<size_t>(std::min(6 - i, 5)), menu.MenuEnd());
+ }
+
+ // Press up button one more time won't change the MenuStart() and MenuEnd().
+ sel = menu.Select(--sel);
+ ASSERT_EQ(0, sel);
+ ASSERT_EQ(0u, menu.MenuStart());
+ ASSERT_EQ(3u, menu.MenuEnd());
+}
+
+TEST_F(ScreenUITest, GraphicMenuSelection) {
+ auto image = GRSurface::Create(50, 50, 50, 1);
+ auto header = image->Clone();
+ std::vector<const GRSurface*> items = {
+ image.get(),
+ image.get(),
+ image.get(),
+ };
+ GraphicMenu menu(header.get(), items, 0, draw_funcs_);
+
+ ASSERT_EQ(0, menu.selection());
+
+ int sel = 0;
+ for (int i = 0; i < 3; i++) {
+ sel = menu.Select(++sel);
+ ASSERT_EQ((i + 1) % 3, sel);
+ ASSERT_EQ(sel, menu.selection());
+ }
+
+ sel = 0;
+ for (int i = 0; i < 3; i++) {
+ sel = menu.Select(--sel);
+ ASSERT_EQ(2 - i, sel);
+ ASSERT_EQ(sel, menu.selection());
+ }
+}
+
+TEST_F(ScreenUITest, GraphicMenuValidate) {
+ auto image = GRSurface::Create(50, 50, 50, 1);
+ auto header = image->Clone();
+ std::vector<const GRSurface*> items = {
+ image.get(),
+ image.get(),
+ image.get(),
+ };
+
+ ASSERT_TRUE(GraphicMenu::Validate(200, 200, header.get(), items));
+
+ // Menu exceeds the horizontal boundary.
+ auto wide_surface = GRSurface::Create(300, 50, 300, 1);
+ ASSERT_FALSE(GraphicMenu::Validate(299, 200, wide_surface.get(), items));
+
+ // Menu exceeds the vertical boundary.
+ items.emplace_back(image.get());
+ ASSERT_FALSE(GraphicMenu::Validate(200, 249, header.get(), items));
+}
+
+static constexpr int kMagicAction = 101;
+
+enum class KeyCode : int {
+ TIMEOUT = -1,
+ NO_OP = 0,
+ UP = 1,
+ DOWN = 2,
+ ENTER = 3,
+ MAGIC = 1001,
+ LAST,
+};
+
+static const std::map<KeyCode, int> kKeyMapping{
+ // clang-format off
+ { KeyCode::NO_OP, Device::kNoAction },
+ { KeyCode::UP, Device::kHighlightUp },
+ { KeyCode::DOWN, Device::kHighlightDown },
+ { KeyCode::ENTER, Device::kInvokeItem },
+ { KeyCode::MAGIC, kMagicAction },
+ // clang-format on
+};
+
+class TestableScreenRecoveryUI : public ScreenRecoveryUI {
+ public:
+ int WaitKey() override;
+
+ void SetKeyBuffer(const std::vector<KeyCode>& buffer);
+
+ int KeyHandler(int key, bool visible) const;
+
+ private:
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, Init);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocale);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation);
+ FRIEND_TEST(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation);
+
+ std::vector<KeyCode> key_buffer_;
+ size_t key_buffer_index_;
+};
+
+void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector<KeyCode>& buffer) {
+ key_buffer_ = buffer;
+ key_buffer_index_ = 0;
+}
+
+int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
+ KeyCode key_code = static_cast<KeyCode>(key);
+ if (kKeyMapping.find(key_code) != kKeyMapping.end()) {
+ return kKeyMapping.at(key_code);
+ }
+ return Device::kNoAction;
+}
+
+int TestableScreenRecoveryUI::WaitKey() {
+ if (IsKeyInterrupted()) {
+ return static_cast<int>(RecoveryUI::KeyError::INTERRUPTED);
+ }
+
+ CHECK_LT(key_buffer_index_, key_buffer_.size());
+ return static_cast<int>(key_buffer_[key_buffer_index_++]);
+}
+
+class DISABLED_ScreenRecoveryUITest : public ::testing::Test {
+ protected:
+ const std::string kTestLocale = "en-US";
+ const std::string kTestRtlLocale = "ar";
+ const std::string kTestRtlLocaleWithSuffix = "ar-EG";
+
+ void SetUp() override {
+ has_graphics_ = gr_init() == 0;
+ gr_exit();
+
+ if (has_graphics_) {
+ ui_ = std::make_unique<TestableScreenRecoveryUI>();
+ }
+
+ testdata_dir_ = from_testdata_base("");
+ Paths::Get().set_resource_dir(testdata_dir_);
+ res_set_resource_dir(testdata_dir_);
+ }
+
+ bool has_graphics_;
+ std::unique_ptr<TestableScreenRecoveryUI> ui_;
+ std::string testdata_dir_;
+};
+
+#define RETURN_IF_NO_GRAPHICS \
+ do { \
+ if (!has_graphics_) { \
+ GTEST_LOG_(INFO) << "Test skipped due to no available graphics device"; \
+ return; \
+ } \
+ } while (false)
+
+TEST_F(DISABLED_ScreenRecoveryUITest, Init) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ASSERT_EQ(kTestLocale, ui_->GetLocale());
+ ASSERT_FALSE(ui_->rtl_locale_);
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ASSERT_FALSE(ui_->WasTextEverVisible());
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, dtor_NotCallingInit) {
+ ui_.reset();
+ ASSERT_FALSE(ui_);
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowText) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ui_->ShowText(true);
+ ASSERT_TRUE(ui_->IsTextVisible());
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+
+ ui_->ShowText(false);
+ ASSERT_FALSE(ui_->IsTextVisible());
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocale) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestRtlLocale));
+ ASSERT_TRUE(ui_->rtl_locale_);
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, RtlLocaleWithSuffix) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
+ ASSERT_TRUE(ui_->rtl_locale_);
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(3u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::UP,
+ KeyCode::NO_OP,
+ KeyCode::NO_OP,
+ KeyCode::UP,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(2u, ui_->ShowMenu(HEADERS, ITEMS, 0, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::MAGIC,
+ });
+ ASSERT_EQ(static_cast<size_t>(kMagicAction),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, false,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::TIMEOUT,
+ });
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::TIMED_OUT),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->ShowText(true);
+ ui_->ShowText(false);
+ ASSERT_TRUE(ui_->WasTextEverVisible());
+
+ ui_->SetKeyBuffer({
+ KeyCode::TIMEOUT,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(4u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, ShowMenuWithInterrupt) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::UP,
+ KeyCode::DOWN,
+ KeyCode::ENTER,
+ });
+
+ ui_->InterruptKey();
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
+ ui_->ShowMenu(HEADERS, ITEMS, 3, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+
+ ui_->SetKeyBuffer({
+ KeyCode::UP,
+ KeyCode::UP,
+ KeyCode::NO_OP,
+ KeyCode::NO_OP,
+ KeyCode::UP,
+ KeyCode::ENTER,
+ });
+ ASSERT_EQ(static_cast<size_t>(RecoveryUI::KeyError::INTERRUPTED),
+ ui_->ShowMenu(HEADERS, ITEMS, 0, true,
+ std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
+ std::placeholders::_1, std::placeholders::_2)));
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ // Make a few copies of loop00000.png from testdata.
+ std::string image_data;
+ ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data));
+
+ std::vector<std::string> tempfiles;
+ TemporaryDir resource_dir;
+ for (const auto& name : { "00002", "00100", "00050" }) {
+ tempfiles.push_back(android::base::StringPrintf("%s/loop%s.png", resource_dir.path, name));
+ ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
+ }
+ for (const auto& name : { "00", "01" }) {
+ tempfiles.push_back(android::base::StringPrintf("%s/intro%s.png", resource_dir.path, name));
+ ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
+ }
+ Paths::Get().set_resource_dir(resource_dir.path);
+
+ ui_->LoadAnimation();
+
+ ASSERT_EQ(2u, ui_->intro_frames_.size());
+ ASSERT_EQ(3u, ui_->loop_frames_.size());
+
+ for (const auto& name : tempfiles) {
+ ASSERT_EQ(0, unlink(name.c_str()));
+ }
+}
+
+TEST_F(DISABLED_ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
+ RETURN_IF_NO_GRAPHICS;
+
+ ASSERT_TRUE(ui_->Init(kTestLocale));
+ // We need a dir that doesn't contain any animation. However, using TemporaryDir will give
+ // leftovers since this is a death test where TemporaryDir::~TemporaryDir() won't be called.
+ Paths::Get().set_resource_dir("/proc/self");
+
+ ::testing::FLAGS_gtest_death_test_style = "threadsafe";
+ ASSERT_EXIT(ui_->LoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
+}
+
+#undef RETURN_IF_NO_GRAPHICS
diff --git a/tests/unit/sysutil_test.cpp b/tests/unit/sysutil_test.cpp
index 434ee25bf..64b8956f7 100644
--- a/tests/unit/sysutil_test.cpp
+++ b/tests/unit/sysutil_test.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
-
#include <string>
#include <android-base/file.h>
-#include <android-base/test_utils.h>
+#include <android-base/strings.h>
+#include <gtest/gtest.h>
-#include "otautil/SysUtil.h"
+#include "otautil/rangeset.h"
+#include "otautil/sysutil.h"
TEST(SysUtilTest, InvalidArgs) {
MemMapping mapping;
@@ -30,6 +30,65 @@ TEST(SysUtilTest, InvalidArgs) {
ASSERT_FALSE(mapping.MapFile(""));
}
+TEST(SysUtilTest, ParseBlockMapFile_smoke) {
+ std::vector<std::string> content = {
+ "/dev/abc", "49652 4096", "3", "1000 1008", "2100 2102", "30 33",
+ };
+
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path));
+
+ auto block_map_data = BlockMapData::ParseBlockMapFile(temp_file.path);
+ ASSERT_EQ("/dev/abc", block_map_data.path());
+ ASSERT_EQ(49652, block_map_data.file_size());
+ ASSERT_EQ(4096, block_map_data.block_size());
+ ASSERT_EQ(RangeSet(std::vector<Range>{
+ { 1000, 1008 },
+ { 2100, 2102 },
+ { 30, 33 },
+ }),
+ block_map_data.block_ranges());
+}
+
+TEST(SysUtilTest, ParseBlockMapFile_invalid_line_count) {
+ std::vector<std::string> content = {
+ "/dev/abc", "49652 4096", "2", "1000 1008", "2100 2102", "30 33",
+ };
+
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path));
+
+ auto block_map_data1 = BlockMapData::ParseBlockMapFile(temp_file.path);
+ ASSERT_FALSE(block_map_data1);
+}
+
+TEST(SysUtilTest, ParseBlockMapFile_invalid_size) {
+ std::vector<std::string> content = {
+ "/dev/abc",
+ "42949672950 4294967295",
+ "1",
+ "0 10",
+ };
+
+ TemporaryFile temp_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path));
+
+ auto block_map_data = BlockMapData::ParseBlockMapFile(temp_file.path);
+ ASSERT_EQ("/dev/abc", block_map_data.path());
+ ASSERT_EQ(42949672950, block_map_data.file_size());
+ ASSERT_EQ(4294967295, block_map_data.block_size());
+
+ content[1] = "42949672950 4294967296";
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path));
+ auto large_block_size = BlockMapData::ParseBlockMapFile(temp_file.path);
+ ASSERT_FALSE(large_block_size);
+
+ content[1] = "4294967296 1";
+ ASSERT_TRUE(android::base::WriteStringToFile(android::base::Join(content, '\n'), temp_file.path));
+ auto too_many_blocks = BlockMapData::ParseBlockMapFile(temp_file.path);
+ ASSERT_FALSE(too_many_blocks);
+}
+
TEST(SysUtilTest, MapFileRegularFile) {
TemporaryFile temp_file1;
std::string content = "abc";
@@ -128,3 +187,13 @@ TEST(SysUtilTest, MapFileBlockMapInvalidBlockMap) {
ASSERT_TRUE(android::base::WriteStringToFile("/doesntexist\n4096 4096\n1\n0 1\n", temp_file.path));
ASSERT_FALSE(mapping.MapFile(filename));
}
+
+TEST(SysUtilTest, StringVectorToNullTerminatedArray) {
+ std::vector<std::string> args{ "foo", "bar", "baz" };
+ auto args_with_nullptr = StringVectorToNullTerminatedArray(args);
+ ASSERT_EQ(4, args_with_nullptr.size());
+ ASSERT_STREQ("foo", args_with_nullptr[0]);
+ ASSERT_STREQ("bar", args_with_nullptr[1]);
+ ASSERT_STREQ("baz", args_with_nullptr[2]);
+ ASSERT_EQ(nullptr, args_with_nullptr[3]);
+}
diff --git a/tests/component/uncrypt_test.cpp b/tests/unit/uncrypt_test.cpp
index 55baca2e3..e97d589a6 100644
--- a/tests/component/uncrypt_test.cpp
+++ b/tests/unit/uncrypt_test.cpp
@@ -26,7 +26,6 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <bootloader_message/bootloader_message.h>
#include <gtest/gtest.h>
diff --git a/tests/unit/update_verifier_test.cpp b/tests/unit/update_verifier_test.cpp
new file mode 100644
index 000000000..e27e58c22
--- /dev/null
+++ b/tests/unit/update_verifier_test.cpp
@@ -0,0 +1,219 @@
+/*
+ * 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 <update_verifier/update_verifier.h>
+
+#include <functional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "care_map.pb.h"
+
+using namespace std::string_literals;
+
+class UpdateVerifierTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ std::string verity_mode = android::base::GetProperty("ro.boot.veritymode", "");
+ verity_supported = android::base::EqualsIgnoreCase(verity_mode, "enforcing");
+
+ care_map_prefix_ = care_map_dir_.path + "/care_map"s;
+ care_map_pb_ = care_map_dir_.path + "/care_map.pb"s;
+ care_map_txt_ = care_map_dir_.path + "/care_map.txt"s;
+ // Overrides the the care_map_prefix.
+ verifier_.set_care_map_prefix(care_map_prefix_);
+
+ property_id_ = "ro.build.fingerprint";
+ fingerprint_ = android::base::GetProperty(property_id_, "");
+ // Overrides the property_reader if we cannot read the given property on the device.
+ if (fingerprint_.empty()) {
+ fingerprint_ = "mock_fingerprint";
+ verifier_.set_property_reader([](const std::string& /* id */) { return "mock_fingerprint"; });
+ }
+ }
+
+ void TearDown() override {
+ unlink(care_map_pb_.c_str());
+ unlink(care_map_txt_.c_str());
+ }
+
+ // Returns a serialized string of the proto3 message according to the given partition info.
+ std::string ConstructProto(
+ std::vector<std::unordered_map<std::string, std::string>>& partitions) {
+ recovery_update_verifier::CareMap result;
+ for (const auto& partition : partitions) {
+ recovery_update_verifier::CareMap::PartitionInfo info;
+ if (partition.find("name") != partition.end()) {
+ info.set_name(partition.at("name"));
+ }
+ if (partition.find("ranges") != partition.end()) {
+ info.set_ranges(partition.at("ranges"));
+ }
+ if (partition.find("id") != partition.end()) {
+ info.set_id(partition.at("id"));
+ }
+ if (partition.find("fingerprint") != partition.end()) {
+ info.set_fingerprint(partition.at("fingerprint"));
+ }
+
+ *result.add_partitions() = info;
+ }
+
+ return result.SerializeAsString();
+ }
+
+ bool verity_supported;
+ UpdateVerifier verifier_;
+
+ TemporaryDir care_map_dir_;
+ std::string care_map_prefix_;
+ std::string care_map_pb_;
+ std::string care_map_txt_;
+
+ std::string property_id_;
+ std::string fingerprint_;
+};
+
+TEST_F(UpdateVerifierTest, verify_image_no_care_map) {
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_text_format) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::string content = "system\n2,0,1";
+ ASSERT_TRUE(android::base::WriteStringToFile(content, care_map_txt_));
+ // CareMap in text format is no longer supported.
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_empty_care_map) {
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_smoke) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_TRUE(verifier_.ParseCareMap());
+ ASSERT_TRUE(verifier_.VerifyPartitions());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_missing_name) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_care_map_bad_ranges) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "3,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", fingerprint_ },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_empty_fingerprint) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
+
+TEST_F(UpdateVerifierTest, verify_image_protobuf_fingerprint_mismatch) {
+ // This test relies on dm-verity support.
+ if (!verity_supported) {
+ GTEST_LOG_(INFO) << "Test skipped on devices without dm-verity support.";
+ return;
+ }
+
+ std::vector<std::unordered_map<std::string, std::string>> partitions = {
+ {
+ { "name", "system" },
+ { "ranges", "2,0,1" },
+ { "id", property_id_ },
+ { "fingerprint", "unsupported_fingerprint" },
+ },
+ };
+
+ std::string proto = ConstructProto(partitions);
+ ASSERT_TRUE(android::base::WriteStringToFile(proto, care_map_pb_));
+ ASSERT_FALSE(verifier_.ParseCareMap());
+}
diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp
new file mode 100644
index 000000000..4a8d1e6ff
--- /dev/null
+++ b/tests/unit/updater_test.cpp
@@ -0,0 +1,1219 @@
+/*
+ * 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 <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <bootloader_message/bootloader_message.h>
+#include <brotli/encode.h>
+#include <bsdiff/bsdiff.h>
+#include <gtest/gtest.h>
+#include <verity/hash_tree_builder.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include "applypatch/applypatch.h"
+#include "common/test_constants.h"
+#include "edify/expr.h"
+#include "otautil/error_code.h"
+#include "otautil/paths.h"
+#include "otautil/print_sha1.h"
+#include "otautil/sysutil.h"
+#include "private/commands.h"
+#include "updater/blockimg.h"
+#include "updater/install.h"
+#include "updater/updater.h"
+
+using namespace std::string_literals;
+
+using PackageEntries = std::unordered_map<std::string, std::string>;
+
+static void expect(const char* expected, const std::string& expr_str, CauseCode cause_code,
+ Updater* updater = nullptr) {
+ std::unique_ptr<Expr> e;
+ int error_count = 0;
+ ASSERT_EQ(0, ParseString(expr_str, &e, &error_count));
+ ASSERT_EQ(0, error_count);
+
+ State state(expr_str, updater);
+
+ std::string result;
+ bool status = Evaluate(&state, e, &result);
+
+ if (expected == nullptr) {
+ ASSERT_FALSE(status);
+ } else {
+ ASSERT_TRUE(status) << "Evaluate() finished with error message: " << state.errmsg;
+ 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 void BuildUpdatePackage(const PackageEntries& entries, int fd) {
+ FILE* zip_file_ptr = fdopen(fd, "wb");
+ ZipWriter zip_writer(zip_file_ptr);
+
+ for (const auto& entry : entries) {
+ // All the entries are written as STORED.
+ ASSERT_EQ(0, zip_writer.StartEntry(entry.first.c_str(), 0));
+ if (!entry.second.empty()) {
+ ASSERT_EQ(0, zip_writer.WriteBytes(entry.second.data(), entry.second.size()));
+ }
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+ }
+
+ ASSERT_EQ(0, zip_writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file_ptr));
+}
+
+static std::string GetSha1(std::string_view content) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest);
+ return print_sha1(digest);
+}
+
+static Value* BlobToString(const char* name, State* state,
+ const std::vector<std::unique_ptr<Expr>>& argv) {
+ if (argv.size() != 1) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size());
+ }
+
+ std::vector<std::unique_ptr<Value>> args;
+ if (!ReadValueArgs(state, argv, &args)) {
+ return nullptr;
+ }
+
+ if (args[0]->type != Value::Type::BLOB) {
+ return ErrorAbort(state, kArgsParsingFailure, "%s() expects a BLOB argument", name);
+ }
+
+ args[0]->type = Value::Type::STRING;
+ return args[0].release();
+}
+
+class UpdaterTestBase {
+ protected:
+ void SetUp() {
+ RegisterBuiltins();
+ RegisterInstallFunctions();
+ RegisterBlockImageFunctions();
+
+ // Each test is run in a separate process (isolated mode). Shared temporary files won't cause
+ // conflicts.
+ Paths::Get().set_cache_temp_source(temp_saved_source_.path);
+ Paths::Get().set_last_command_file(temp_last_command_.path);
+ Paths::Get().set_stash_directory_base(temp_stash_base_.path);
+
+ last_command_file_ = temp_last_command_.path;
+ image_file_ = image_temp_file_.path;
+ }
+
+ void TearDown() {
+ // Clean up the last_command_file if any.
+ ASSERT_TRUE(android::base::RemoveFileIfExists(last_command_file_));
+
+ // Clear partition updated marker if any.
+ std::string updated_marker{ temp_stash_base_.path };
+ updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED";
+ ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker));
+ }
+
+ void RunBlockImageUpdate(bool is_verify, PackageEntries entries, const std::string& image_file,
+ const std::string& result, CauseCode cause_code = kNoCause) {
+ CHECK(entries.find("transfer_list") != entries.end());
+ std::string new_data =
+ entries.find("new_data.br") != entries.end() ? "new_data.br" : "new_data";
+ std::string script = is_verify ? "block_image_verify" : "block_image_update";
+ script += R"((")" + image_file + R"(", package_extract_file("transfer_list"), ")" + new_data +
+ R"(", "patch_data"))";
+ entries.emplace(Updater::SCRIPT_NAME, script);
+
+ // Build the update package.
+ TemporaryFile zip_file;
+ BuildUpdatePackage(entries, zip_file.release());
+
+ // Set up the handler, command_pipe, patch offset & length.
+ TemporaryFile temp_pipe;
+ ASSERT_TRUE(updater_.Init(temp_pipe.release(), zip_file.path, false, nullptr));
+ ASSERT_TRUE(updater_.RunUpdate());
+ ASSERT_EQ(result, updater_.result());
+
+ // Parse the cause code written to the command pipe.
+ int received_cause_code = kNoCause;
+ std::string pipe_content;
+ ASSERT_TRUE(android::base::ReadFileToString(temp_pipe.path, &pipe_content));
+ auto lines = android::base::Split(pipe_content, "\n");
+ for (std::string_view line : lines) {
+ if (android::base::ConsumePrefix(&line, "log cause: ")) {
+ ASSERT_TRUE(android::base::ParseInt(line.data(), &received_cause_code));
+ }
+ }
+ ASSERT_EQ(cause_code, received_cause_code);
+ }
+
+ TemporaryFile temp_saved_source_;
+ TemporaryDir temp_stash_base_;
+ std::string last_command_file_;
+ std::string image_file_;
+
+ Updater updater_;
+
+ private:
+ TemporaryFile temp_last_command_;
+ TemporaryFile image_temp_file_;
+};
+
+class UpdaterTest : public UpdaterTestBase, public ::testing::Test {
+ protected:
+ void SetUp() override {
+ UpdaterTestBase::SetUp();
+
+ RegisterFunction("blob_to_string", BlobToString);
+ // Enable a special command "abort" to simulate interruption.
+ Command::abort_allowed_ = true;
+ }
+
+ void TearDown() override {
+ UpdaterTestBase::TearDown();
+ }
+
+ void SetUpdaterCmdPipe(int fd) {
+ FILE* cmd_pipe = fdopen(fd, "w");
+ ASSERT_NE(nullptr, cmd_pipe);
+ updater_.cmd_pipe_.reset(cmd_pipe);
+ }
+
+ void SetUpdaterOtaPackageHandle(ZipArchiveHandle handle) {
+ updater_.package_handle_ = handle;
+ }
+
+ void FlushUpdaterCommandPipe() const {
+ fflush(updater_.cmd_pipe_.get());
+ }
+};
+
+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, patch_partition_check) {
+ // Zero argument is not valid.
+ expect(nullptr, "patch_partition_check()", kArgsParsingFailure);
+
+ std::string source_file = from_testdata_base("boot.img");
+ std::string source_content;
+ ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content));
+ size_t source_size = source_content.size();
+ std::string source_hash = GetSha1(source_content);
+ Partition source(source_file, source_size, source_hash);
+
+ std::string target_file = from_testdata_base("recovery.img");
+ std::string target_content;
+ ASSERT_TRUE(android::base::ReadFileToString(target_file, &target_content));
+ size_t target_size = target_content.size();
+ std::string target_hash = GetSha1(target_content);
+ Partition target(target_file, target_size, target_hash);
+
+ // One argument is not valid.
+ expect(nullptr, "patch_partition_check(\"" + source.ToString() + "\")", kArgsParsingFailure);
+ expect(nullptr, "patch_partition_check(\"" + target.ToString() + "\")", kArgsParsingFailure);
+
+ // Both of the source and target have the desired checksum.
+ std::string cmd =
+ "patch_partition_check(\"" + source.ToString() + "\", \"" + target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Only source partition has the desired checksum.
+ Partition bad_target(target_file, target_size - 1, target_hash);
+ cmd = "patch_partition_check(\"" + source.ToString() + "\", \"" + bad_target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Only target partition has the desired checksum.
+ Partition bad_source(source_file, source_size + 1, source_hash);
+ cmd = "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + target.ToString() + "\")";
+ expect("t", cmd, kNoCause);
+
+ // Neither of the source or target has the desired checksum.
+ cmd =
+ "patch_partition_check(\"" + bad_source.ToString() + "\", \"" + bad_target.ToString() + "\")";
+ expect("", cmd, 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\")", kFreadFailure);
+
+ // 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, kNoCause);
+
+ std::string script2("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.board\")");
+ expect("magic", script2, kNoCause);
+
+ // No match.
+ std::string script3("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.wrong\")");
+ expect("", script3, kNoCause);
+
+ std::string script4("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.name=\")");
+ expect("", script4, kNoCause);
+
+ std::string script5("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.nam\")");
+ expect("", script5, kNoCause);
+
+ std::string script6("file_getprop(\"" + std::string(temp_file2.path) +
+ "\", \"ro.product.model\")");
+ expect("", script6, kNoCause);
+}
+
+// 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.
+ SetUpdaterOtaPackageHandle(handle);
+
+ // Two-argument version.
+ TemporaryFile temp_file1;
+ std::string script("package_extract_file(\"a.txt\", \"" + std::string(temp_file1.path) + "\")");
+ expect("t", script, kNoCause, &updater_);
+
+ // 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, kNoCause, &updater_);
+
+ 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, kNoCause, &updater_);
+
+ // Extract to /dev/full should fail.
+ script = "package_extract_file(\"a.txt\", \"/dev/full\")";
+ expect("", script, kNoCause, &updater_);
+
+ // One-argument version. package_extract_file() gives a VAL_BLOB, which needs to be converted to
+ // VAL_STRING for equality test.
+ script = "blob_to_string(package_extract_file(\"a.txt\")) == \"" + kATxtContents + "\"";
+ expect("t", script, kNoCause, &updater_);
+
+ script = "blob_to_string(package_extract_file(\"b.txt\")) == \"" + kBTxtContents + "\"";
+ expect("t", script, kNoCause, &updater_);
+
+ // Missing entry. The one-argument version aborts the evaluation.
+ script = "package_extract_file(\"doesntexist\")";
+ expect(nullptr, script, kPackageExtractFileFailure, &updater_);
+}
+
+TEST_F(UpdaterTest, read_file) {
+ // read_file() expects one argument.
+ expect(nullptr, "read_file()", kArgsParsingFailure);
+ expect(nullptr, "read_file(\"arg1\", \"arg2\")", kArgsParsingFailure);
+
+ // Write some value to file and read back.
+ TemporaryFile temp_file;
+ std::string script("write_value(\"foo\", \""s + temp_file.path + "\");");
+ expect("t", script, kNoCause);
+
+ script = "read_file(\""s + temp_file.path + "\") == \"foo\"";
+ expect("t", script, kNoCause);
+
+ script = "read_file(\""s + temp_file.path + "\") == \"bar\"";
+ expect("", script, kNoCause);
+
+ // It should fail gracefully when read fails.
+ script = "read_file(\"/doesntexist\")";
+ expect("", script, kNoCause);
+}
+
+TEST_F(UpdaterTest, compute_hash_tree_smoke) {
+ std::string data;
+ for (unsigned char i = 0; i < 128; i++) {
+ data += std::string(4096, i);
+ }
+ // Appends an additional block for verity data.
+ data += std::string(4096, 0);
+ ASSERT_EQ(129 * 4096, data.size());
+ ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
+
+ std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
+ std::string expected_root_hash =
+ "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
+ // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
+ std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
+ expected_root_hash };
+ std::string hash_tree_command = android::base::Join(tokens, " ");
+
+ std::vector<std::string> transfer_list{
+ "4", "2", "0", "2", hash_tree_command,
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, "\n") },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+
+ std::string updated;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
+ ASSERT_EQ(129 * 4096, updated.size());
+ ASSERT_EQ(data.substr(0, 128 * 4096), updated.substr(0, 128 * 4096));
+
+ // Computes the SHA256 of the salt + hash_tree_data and expects the result to match with the
+ // root_hash.
+ std::vector<unsigned char> salt_bytes;
+ ASSERT_TRUE(HashTreeBuilder::ParseBytesArrayFromString(salt, &salt_bytes));
+ std::vector<unsigned char> hash_tree = std::move(salt_bytes);
+ hash_tree.insert(hash_tree.end(), updated.begin() + 128 * 4096, updated.end());
+
+ std::vector<unsigned char> digest(SHA256_DIGEST_LENGTH);
+ SHA256(hash_tree.data(), hash_tree.size(), digest.data());
+ ASSERT_EQ(expected_root_hash, HashTreeBuilder::BytesArrayToString(digest));
+}
+
+TEST_F(UpdaterTest, compute_hash_tree_root_mismatch) {
+ std::string data;
+ for (size_t i = 0; i < 128; i++) {
+ data += std::string(4096, i);
+ }
+ // Appends an additional block for verity data.
+ data += std::string(4096, 0);
+ ASSERT_EQ(129 * 4096, data.size());
+ // Corrupts one bit
+ data[4096] = 'A';
+ ASSERT_TRUE(android::base::WriteStringToFile(data, image_file_));
+
+ std::string salt = "aee087a5be3b982978c923f566a94613496b417f2af592639bc80d141e34dfe7";
+ std::string expected_root_hash =
+ "7e0a8d8747f54384014ab996f5b2dc4eb7ff00c630eede7134c9e3f05c0dd8ca";
+ // hash_tree_ranges, source_ranges, hash_algorithm, salt_hex, root_hash
+ std::vector<std::string> tokens{ "compute_hash_tree", "2,128,129", "2,0,128", "sha256", salt,
+ expected_root_hash };
+ std::string hash_tree_command = android::base::Join(tokens, " ");
+
+ std::vector<std::string> transfer_list{
+ "4", "2", "0", "2", hash_tree_command,
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, "\n") },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "", kHashTreeComputationFailure);
+}
+
+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, 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, 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, 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, kNoCause);
+
+ // Bad BCB path.
+ script = "get_stage(\"doesntexist\")";
+ expect("", script, 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, 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, kNoCause);
+
+ script = "set_stage(\"/dev/full\", \"1/3\")";
+ expect("", script, 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;
+ SetUpdaterCmdPipe(tf.release());
+ expect(".52", "set_progress(\".52\")", kNoCause, &updater_);
+ FlushUpdaterCommandPipe();
+
+ 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;
+ SetUpdaterCmdPipe(tf.release());
+ expect(".52", "show_progress(\".52\", \"10\")", kNoCause, &updater_);
+ FlushUpdaterCommandPipe();
+
+ 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_parsing_error) {
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ // clang-format on
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure);
+}
+
+// Generates the bsdiff of the given source and target images, and writes the result entries.
+// target_blocks specifies the block count to be written into the `bsdiff` command, which may be
+// different from the given target size in order to trigger overrun / underrun paths.
+static void GetEntriesForBsdiff(std::string_view source, std::string_view target,
+ size_t target_blocks, PackageEntries* entries) {
+ // Generate the patch data.
+ TemporaryFile patch_file;
+ ASSERT_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(source.data()), source.size(),
+ reinterpret_cast<const uint8_t*>(target.data()), target.size(),
+ patch_file.path, nullptr));
+ std::string patch_content;
+ ASSERT_TRUE(android::base::ReadFileToString(patch_file.path, &patch_content));
+
+ // Create the transfer list that contains a bsdiff.
+ std::string src_hash = GetSha1(source);
+ std::string tgt_hash = GetSha1(target);
+ size_t source_blocks = source.size() / 4096;
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ std::to_string(target_blocks),
+ "0",
+ "0",
+ // bsdiff patch_offset patch_length source_hash target_hash target_range source_block_count
+ // source_range
+ android::base::StringPrintf("bsdiff 0 %zu %s %s 2,0,%zu %zu 2,0,%zu", patch_content.size(),
+ src_hash.c_str(), tgt_hash.c_str(), target_blocks, source_blocks,
+ source_blocks),
+ // clang-format on
+ };
+
+ *entries = {
+ { "new_data", "" },
+ { "patch_data", patch_content },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_data) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 2, &entries);
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+
+ // The update_file should be patched correctly.
+ std::string updated;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated));
+ ASSERT_EQ(target, updated);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_overrun) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ // Provide one less block to trigger the overrun path.
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 1, &entries);
+
+ // The update should fail due to overrun.
+ RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
+}
+
+TEST_F(UpdaterTest, block_image_update_patch_underrun) {
+ // Both source and target images have 10 blocks.
+ std::string source =
+ std::string(4096, 'a') + std::string(4096, 'c') + std::string(4096 * 3, '\0');
+ std::string target =
+ std::string(4096, 'b') + std::string(4096, 'd') + std::string(4096 * 3, '\0');
+ ASSERT_TRUE(android::base::WriteStringToFile(source, image_file_));
+
+ // Provide one more block to trigger the overrun path.
+ PackageEntries entries;
+ GetEntriesForBsdiff(std::string_view(source).substr(0, 4096 * 2),
+ std::string_view(target).substr(0, 4096 * 2), 3, &entries);
+
+ // The update should fail due to underrun.
+ RunBlockImageUpdate(false, entries, image_file_, "", kPatchApplicationFailure);
+}
+
+TEST_F(UpdaterTest, block_image_update_fail) {
+ std::string src_content(4096 * 2, 'e');
+ std::string src_hash = GetSha1(src_content);
+ // Stash and free some blocks, then fail the update intentionally.
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + src_hash + " 2,0,2",
+ "free " + src_hash,
+ "abort",
+ // clang-format on
+ };
+
+ // Add a new data of 10 bytes to test the deadlock.
+ PackageEntries entries{
+ { "new_data", std::string(10, 0) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_));
+
+ RunBlockImageUpdate(false, entries, image_file_, "");
+
+ // Updater generates the stash name based on the input file name.
+ std::string name_digest = GetSha1(image_file_);
+ std::string stash_base = std::string(temp_stash_base_.path) + "/" + name_digest;
+ ASSERT_EQ(0, access(stash_base.c_str(), F_OK));
+ // Expect the stashed blocks to be freed.
+ ASSERT_EQ(-1, access((stash_base + src_hash).c_str(), F_OK));
+ ASSERT_EQ(0, rmdir(stash_base.c_str()));
+}
+
+TEST_F(UpdaterTest, new_data_over_write) {
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "1",
+ "0",
+ "0",
+ "new 2,0,1",
+ // clang-format on
+ };
+
+ // Write 4096 + 100 bytes of new data.
+ PackageEntries entries{
+ { "new_data", std::string(4196, 0) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+}
+
+TEST_F(UpdaterTest, new_data_short_write) {
+ std::vector<std::string> transfer_list{
+ // clang-format off
+ "4",
+ "1",
+ "0",
+ "0",
+ "new 2,0,1",
+ // clang-format on
+ };
+
+ PackageEntries entries{
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ // Updater should report the failure gracefully rather than stuck in deadlock.
+ entries["new_data"] = "";
+ RunBlockImageUpdate(false, entries, image_file_, "");
+
+ entries["new_data"] = std::string(10, 'a');
+ RunBlockImageUpdate(false, entries, image_file_, "");
+
+ // Expect to write 1 block of new data successfully.
+ entries["new_data"] = std::string(4096, 'a');
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+}
+
+TEST_F(UpdaterTest, brotli_new_data) {
+ auto generator = []() { return rand() % 128; };
+ // Generate 100 blocks of random data.
+ std::string brotli_new_data;
+ brotli_new_data.reserve(4096 * 100);
+ generate_n(back_inserter(brotli_new_data), 4096 * 100, generator);
+
+ size_t encoded_size = BrotliEncoderMaxCompressedSize(brotli_new_data.size());
+ std::string encoded_data(encoded_size, 0);
+ ASSERT_TRUE(BrotliEncoderCompress(
+ BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, brotli_new_data.size(),
+ reinterpret_cast<const uint8_t*>(brotli_new_data.data()), &encoded_size,
+ reinterpret_cast<uint8_t*>(const_cast<char*>(encoded_data.data()))));
+ encoded_data.resize(encoded_size);
+
+ // Write a few small chunks of new data, then a large chunk, and finally a few small chunks.
+ // This helps us to catch potential short writes.
+ std::vector<std::string> transfer_list = {
+ "4",
+ "100",
+ "0",
+ "0",
+ "new 2,0,1",
+ "new 2,1,2",
+ "new 4,2,50,50,97",
+ "new 2,97,98",
+ "new 2,98,99",
+ "new 2,99,100",
+ };
+
+ PackageEntries entries{
+ { "new_data.br", std::move(encoded_data) },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list, '\n') },
+ };
+
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+
+ std::string updated_content;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content));
+ ASSERT_EQ(brotli_new_data, updated_content);
+}
+
+TEST_F(UpdaterTest, last_command_update) {
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
+ std::string block3(4096, '3');
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
+ std::string block3_hash = GetSha1(block3);
+
+ // Compose the transfer list to fail the first update.
+ std::vector<std::string> transfer_list_fail{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ "abort",
+ // clang-format on
+ };
+
+ // Mimic a resumed update with the same transfer commands.
+ std::vector<std::string> transfer_list_continue{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ "move " + block1_hash + " 2,2,3 1 2,0,1",
+ // clang-format on
+ };
+
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list_fail, '\n') },
+ };
+
+ // "2\nstash " + block3_hash + " 2,2,3"
+ std::string last_command_content =
+ "2\n" + transfer_list_fail[TransferList::kTransferListHeaderLines + 2];
+
+ RunBlockImageUpdate(false, entries, image_file_, "");
+
+ // Expect last_command to contain the last stash command.
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ EXPECT_EQ(last_command_content, last_command_actual);
+
+ std::string updated_contents;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
+ ASSERT_EQ(block1 + block1 + block3, updated_contents);
+
+ // "Resume" the update. Expect the first 'move' to be skipped but the second 'move' to be
+ // executed. Note that we intentionally reset the image file.
+ entries["transfer_list"] = android::base::Join(transfer_list_continue, '\n');
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+ RunBlockImageUpdate(false, entries, image_file_, "t");
+
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_contents));
+ ASSERT_EQ(block1 + block2 + block1, updated_contents);
+}
+
+TEST_F(UpdaterTest, last_command_update_unresumable) {
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
+
+ // Construct an unresumable update with source blocks mismatch.
+ std::vector<std::string> transfer_list_unresumable{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block2_hash + " 2,1,2 1 2,0,1",
+ // clang-format on
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list_unresumable, '\n') },
+ };
+
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1, image_file_));
+
+ std::string last_command_content =
+ "0\n" + transfer_list_unresumable[TransferList::kTransferListHeaderLines];
+ ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
+
+ RunBlockImageUpdate(false, entries, image_file_, "");
+
+ // The last_command_file will be deleted if the update encounters an unresumable failure later.
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+}
+
+TEST_F(UpdaterTest, last_command_verify) {
+ std::string block1(4096, '1');
+ std::string block2(4096, '2');
+ std::string block3(4096, '3');
+ std::string block1_hash = GetSha1(block1);
+ std::string block2_hash = GetSha1(block2);
+ std::string block3_hash = GetSha1(block3);
+
+ std::vector<std::string> transfer_list_verify{
+ // clang-format off
+ "4",
+ "2",
+ "0",
+ "2",
+ "stash " + block1_hash + " 2,0,1",
+ "move " + block1_hash + " 2,0,1 1 2,0,1",
+ "move " + block1_hash + " 2,1,2 1 2,0,1",
+ "stash " + block3_hash + " 2,2,3",
+ // clang-format on
+ };
+
+ PackageEntries entries{
+ { "new_data", "" },
+ { "patch_data", "" },
+ { "transfer_list", android::base::Join(transfer_list_verify, '\n') },
+ };
+
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block1 + block3, image_file_));
+
+ // Last command: "move " + block1_hash + " 2,1,2 1 2,0,1"
+ std::string last_command_content =
+ "2\n" + transfer_list_verify[TransferList::kTransferListHeaderLines + 2];
+
+ // First run: expect the verification to succeed and the last_command_file is intact.
+ ASSERT_TRUE(android::base::WriteStringToFile(last_command_content, last_command_file_));
+
+ RunBlockImageUpdate(true, entries, image_file_, "t");
+
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ EXPECT_EQ(last_command_content, last_command_actual);
+
+ // Second run with a mismatching block image: expect the verification to succeed but
+ // last_command_file to be deleted; because the target blocks in the last command don't have the
+ // expected contents for the second move command.
+ ASSERT_TRUE(android::base::WriteStringToFile(block1 + block2 + block3, image_file_));
+ RunBlockImageUpdate(true, entries, image_file_, "t");
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+}
+
+class ResumableUpdaterTest : public UpdaterTestBase, public testing::TestWithParam<size_t> {
+ protected:
+ void SetUp() override {
+ UpdaterTestBase::SetUp();
+ // Enable a special command "abort" to simulate interruption.
+ Command::abort_allowed_ = true;
+ index_ = GetParam();
+ }
+
+ void TearDown() override {
+ UpdaterTestBase::TearDown();
+ }
+
+ size_t index_;
+};
+
+static std::string g_source_image;
+static std::string g_target_image;
+static PackageEntries g_entries;
+
+static std::vector<std::string> GenerateTransferList() {
+ std::string a(4096, 'a');
+ std::string b(4096, 'b');
+ std::string c(4096, 'c');
+ std::string d(4096, 'd');
+ std::string e(4096, 'e');
+ std::string f(4096, 'f');
+ std::string g(4096, 'g');
+ std::string h(4096, 'h');
+ std::string i(4096, 'i');
+ std::string zero(4096, '\0');
+
+ std::string a_hash = GetSha1(a);
+ std::string b_hash = GetSha1(b);
+ std::string c_hash = GetSha1(c);
+ std::string e_hash = GetSha1(e);
+
+ auto loc = [](const std::string& range_text) {
+ std::vector<std::string> pieces = android::base::Split(range_text, "-");
+ size_t left;
+ size_t right;
+ if (pieces.size() == 1) {
+ CHECK(android::base::ParseUint(pieces[0], &left));
+ right = left + 1;
+ } else {
+ CHECK_EQ(2u, pieces.size());
+ CHECK(android::base::ParseUint(pieces[0], &left));
+ CHECK(android::base::ParseUint(pieces[1], &right));
+ right++;
+ }
+ return android::base::StringPrintf("2,%zu,%zu", left, right);
+ };
+
+ // patch 1: "b d c" -> "g"
+ TemporaryFile patch_file_bdc_g;
+ std::string bdc = b + d + c;
+ std::string bdc_hash = GetSha1(bdc);
+ std::string g_hash = GetSha1(g);
+ CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(bdc.data()), bdc.size(),
+ reinterpret_cast<const uint8_t*>(g.data()), g.size(),
+ patch_file_bdc_g.path, nullptr));
+ std::string patch_bdc_g;
+ CHECK(android::base::ReadFileToString(patch_file_bdc_g.path, &patch_bdc_g));
+
+ // patch 2: "a b c d" -> "d c b"
+ TemporaryFile patch_file_abcd_dcb;
+ std::string abcd = a + b + c + d;
+ std::string abcd_hash = GetSha1(abcd);
+ std::string dcb = d + c + b;
+ std::string dcb_hash = GetSha1(dcb);
+ CHECK_EQ(0, bsdiff::bsdiff(reinterpret_cast<const uint8_t*>(abcd.data()), abcd.size(),
+ reinterpret_cast<const uint8_t*>(dcb.data()), dcb.size(),
+ patch_file_abcd_dcb.path, nullptr));
+ std::string patch_abcd_dcb;
+ CHECK(android::base::ReadFileToString(patch_file_abcd_dcb.path, &patch_abcd_dcb));
+
+ std::vector<std::string> transfer_list{
+ "4",
+ "10", // total blocks written
+ "2", // maximum stash entries
+ "2", // maximum number of stashed blocks
+
+ // a b c d e a b c d e
+ "stash " + b_hash + " " + loc("1"),
+ // a b c d e a b c d e [b(1)]
+ "stash " + c_hash + " " + loc("2"),
+ // a b c d e a b c d e [b(1)][c(2)]
+ "new " + loc("1-2"),
+ // a i h d e a b c d e [b(1)][c(2)]
+ "zero " + loc("0"),
+ // 0 i h d e a b c d e [b(1)][c(2)]
+
+ // bsdiff "b d c" (from stash, 3, stash) to get g(3)
+ android::base::StringPrintf(
+ "bsdiff 0 %zu %s %s %s 3 %s %s %s:%s %s:%s",
+ patch_bdc_g.size(), // patch start (0), patch length
+ bdc_hash.c_str(), // source hash
+ g_hash.c_str(), // target hash
+ loc("3").c_str(), // target range
+ loc("3").c_str(), loc("1").c_str(), // load "d" from block 3, into buffer at offset 1
+ b_hash.c_str(), loc("0").c_str(), // load "b" from stash, into buffer at offset 0
+ c_hash.c_str(), loc("2").c_str()), // load "c" from stash, into buffer at offset 2
+
+ // 0 i h g e a b c d e [b(1)][c(2)]
+ "free " + b_hash,
+ // 0 i h g e a b c d e [c(2)]
+ "free " + a_hash,
+ // 0 i h g e a b c d e
+ "stash " + a_hash + " " + loc("5"),
+ // 0 i h g e a b c d e [a(5)]
+ "move " + e_hash + " " + loc("5") + " 1 " + loc("4"),
+ // 0 i h g e e b c d e [a(5)]
+
+ // bsdiff "a b c d" (from stash, 6-8) to "d c b" (6-8)
+ android::base::StringPrintf( //
+ "bsdiff %zu %zu %s %s %s 4 %s %s %s:%s",
+ patch_bdc_g.size(), // patch start
+ patch_bdc_g.size() + patch_abcd_dcb.size(), // patch length
+ abcd_hash.c_str(), // source hash
+ dcb_hash.c_str(), // target hash
+ loc("6-8").c_str(), // target range
+ loc("6-8").c_str(), // load "b c d" from blocks 6-8
+ loc("1-3").c_str(), // into buffer at offset 1-3
+ a_hash.c_str(), // load "a" from stash
+ loc("0").c_str()), // into buffer at offset 0
+
+ // 0 i h g e e d c b e [a(5)]
+ "new " + loc("4"),
+ // 0 i h g f e d c b e [a(5)]
+ "move " + a_hash + " " + loc("9") + " 1 - " + a_hash + ":" + loc("0"),
+ // 0 i h g f e d c b a [a(5)]
+ "free " + a_hash,
+ // 0 i h g f e d c b a
+ };
+
+ std::string new_data = i + h + f;
+ std::string patch_data = patch_bdc_g + patch_abcd_dcb;
+
+ g_entries = {
+ { "new_data", new_data },
+ { "patch_data", patch_data },
+ };
+ g_source_image = a + b + c + d + e + a + b + c + d + e;
+ g_target_image = zero + i + h + g + f + e + d + c + b + a;
+
+ return transfer_list;
+}
+
+static const std::vector<std::string> g_transfer_list = GenerateTransferList();
+
+INSTANTIATE_TEST_CASE_P(InterruptAfterEachCommand, ResumableUpdaterTest,
+ ::testing::Range(static_cast<size_t>(0),
+ g_transfer_list.size() -
+ TransferList::kTransferListHeaderLines));
+
+TEST_P(ResumableUpdaterTest, InterruptVerifyResume) {
+ ASSERT_TRUE(android::base::WriteStringToFile(g_source_image, image_file_));
+
+ LOG(INFO) << "Interrupting at line " << index_ << " ("
+ << g_transfer_list[TransferList::kTransferListHeaderLines + index_] << ")";
+
+ std::vector<std::string> transfer_list_copy{ g_transfer_list };
+ transfer_list_copy[TransferList::kTransferListHeaderLines + index_] = "abort";
+
+ g_entries["transfer_list"] = android::base::Join(transfer_list_copy, '\n');
+
+ // Run update that's expected to fail.
+ RunBlockImageUpdate(false, g_entries, image_file_, "");
+
+ std::string last_command_expected;
+
+ // Assert the last_command_file.
+ if (index_ == 0) {
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+ } else {
+ last_command_expected = std::to_string(index_ - 1) + "\n" +
+ g_transfer_list[TransferList::kTransferListHeaderLines + index_ - 1];
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ ASSERT_EQ(last_command_expected, last_command_actual);
+ }
+
+ g_entries["transfer_list"] = android::base::Join(g_transfer_list, '\n');
+
+ // Resume the interrupted update, by doing verification first.
+ RunBlockImageUpdate(true, g_entries, image_file_, "t");
+
+ // last_command_file should remain intact.
+ if (index_ == 0) {
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+ } else {
+ std::string last_command_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(last_command_file_, &last_command_actual));
+ ASSERT_EQ(last_command_expected, last_command_actual);
+ }
+
+ // Resume the update.
+ RunBlockImageUpdate(false, g_entries, image_file_, "t");
+
+ // last_command_file should be gone after successful update.
+ ASSERT_EQ(-1, access(last_command_file_.c_str(), R_OK));
+
+ std::string updated_image_actual;
+ ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_image_actual));
+ ASSERT_EQ(g_target_image, updated_image_actual);
+}
diff --git a/tests/unit/verifier_test.cpp b/tests/unit/verifier_test.cpp
new file mode 100644
index 000000000..ded23c52f
--- /dev/null
+++ b/tests/unit/verifier_test.cpp
@@ -0,0 +1,364 @@
+/*
+ * 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 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <openssl/bn.h>
+#include <openssl/ec.h>
+#include <openssl/nid.h>
+#include <ziparchive/zip_writer.h>
+
+#include "common/test_constants.h"
+#include "install/package.h"
+#include "install/verifier.h"
+#include "otautil/sysutil.h"
+
+using namespace std::string_literals;
+
+static void LoadKeyFromFile(const std::string& file_name, Certificate* cert) {
+ std::string testkey_string;
+ ASSERT_TRUE(android::base::ReadFileToString(file_name, &testkey_string));
+ ASSERT_TRUE(LoadCertificateFromBuffer(
+ std::vector<uint8_t>(testkey_string.begin(), testkey_string.end()), cert));
+}
+
+static void VerifyFile(const std::string& content, const std::vector<Certificate>& keys,
+ int expected) {
+ auto package =
+ Package::CreateMemoryPackage(std::vector<uint8_t>(content.begin(), content.end()), nullptr);
+ ASSERT_NE(nullptr, package);
+
+ ASSERT_EQ(expected, verify_file(package.get(), keys));
+}
+
+static void VerifyPackageWithCertificates(const std::string& name,
+ const std::vector<Certificate>& certs) {
+ std::string path = from_testdata_base(name);
+ auto package = Package::CreateMemoryPackage(path, nullptr);
+ ASSERT_NE(nullptr, package);
+
+ ASSERT_EQ(VERIFY_SUCCESS, verify_file(package.get(), certs));
+}
+
+static void VerifyPackageWithSingleCertificate(const std::string& name, Certificate&& cert) {
+ std::vector<Certificate> certs;
+ certs.emplace_back(std::move(cert));
+ VerifyPackageWithCertificates(name, certs);
+}
+
+static void BuildCertificateArchive(const std::vector<std::string>& file_names, int fd) {
+ FILE* zip_file_ptr = fdopen(fd, "wb");
+ ZipWriter zip_writer(zip_file_ptr);
+
+ for (const auto& name : file_names) {
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFileToString(name, &content));
+
+ // Makes sure the zip entry name has the correct suffix.
+ std::string entry_name = name;
+ if (!android::base::EndsWith(entry_name, "x509.pem")) {
+ entry_name += "x509.pem";
+ }
+ ASSERT_EQ(0, zip_writer.StartEntry(entry_name.c_str(), ZipWriter::kCompress));
+ ASSERT_EQ(0, zip_writer.WriteBytes(content.data(), content.size()));
+ ASSERT_EQ(0, zip_writer.FinishEntry());
+ }
+
+ ASSERT_EQ(0, zip_writer.Finish());
+ ASSERT_EQ(0, fclose(zip_file_ptr));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_failure) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ std::string testkey_string;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(from_testdata_base("testkey_v1.txt"), &testkey_string));
+ ASSERT_FALSE(LoadCertificateFromBuffer(
+ std::vector<uint8_t>(testkey_string.begin(), testkey_string.end()), &cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent3) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v1.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type);
+ ASSERT_EQ(nullptr, cert.ec);
+
+ VerifyPackageWithSingleCertificate("otasigned_v1.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent65537) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v2.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type);
+ ASSERT_EQ(nullptr, cert.ec);
+
+ VerifyPackageWithSingleCertificate("otasigned_v2.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent3) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type);
+ ASSERT_EQ(nullptr, cert.ec);
+
+ VerifyPackageWithSingleCertificate("otasigned_v3.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent65537) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v4.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type);
+ ASSERT_EQ(nullptr, cert.ec);
+
+ VerifyPackageWithSingleCertificate("otasigned_v4.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha256_ec256bits) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v5.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_EC, cert.key_type);
+ ASSERT_EQ(nullptr, cert.rsa);
+
+ VerifyPackageWithSingleCertificate("otasigned_v5.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_sha256_rsa4096_bits) {
+ Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_4096bits.x509.pem"), &cert);
+
+ ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len);
+ ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type);
+ ASSERT_EQ(nullptr, cert.ec);
+
+ VerifyPackageWithSingleCertificate("otasigned_4096bits.zip", std::move(cert));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_check_rsa_keys) {
+ std::unique_ptr<RSA, RSADeleter> rsa(RSA_new());
+ std::unique_ptr<BIGNUM, decltype(&BN_free)> exponent(BN_new(), BN_free);
+ BN_set_word(exponent.get(), 3);
+ RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr);
+ ASSERT_TRUE(CheckRSAKey(rsa));
+
+ // Exponent is expected to be 3 or 65537
+ BN_set_word(exponent.get(), 17);
+ RSA_generate_key_ex(rsa.get(), 2048, exponent.get(), nullptr);
+ ASSERT_FALSE(CheckRSAKey(rsa));
+
+ // Modulus is expected to be 2048.
+ BN_set_word(exponent.get(), 3);
+ RSA_generate_key_ex(rsa.get(), 1024, exponent.get(), nullptr);
+ ASSERT_FALSE(CheckRSAKey(rsa));
+}
+
+TEST(VerifierTest, LoadCertificateFromBuffer_check_ec_keys) {
+ std::unique_ptr<EC_KEY, ECKEYDeleter> ec(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ ASSERT_EQ(1, EC_KEY_generate_key(ec.get()));
+ ASSERT_TRUE(CheckECKey(ec));
+
+ // Expects 256-bit EC key with curve NIST P-256
+ ec.reset(EC_KEY_new_by_curve_name(NID_secp224r1));
+ ASSERT_EQ(1, EC_KEY_generate_key(ec.get()));
+ ASSERT_FALSE(CheckECKey(ec));
+}
+
+TEST(VerifierTest, LoadKeysFromZipfile_empty_archive) {
+ TemporaryFile otacerts;
+ BuildCertificateArchive({}, otacerts.release());
+ std::vector<Certificate> certs = LoadKeysFromZipfile(otacerts.path);
+ ASSERT_TRUE(certs.empty());
+}
+
+TEST(VerifierTest, LoadKeysFromZipfile_single_key) {
+ TemporaryFile otacerts;
+ BuildCertificateArchive({ from_testdata_base("testkey_v1.x509.pem") }, otacerts.release());
+ std::vector<Certificate> certs = LoadKeysFromZipfile(otacerts.path);
+ ASSERT_EQ(1, certs.size());
+
+ VerifyPackageWithCertificates("otasigned_v1.zip", certs);
+}
+
+TEST(VerifierTest, LoadKeysFromZipfile_corrupted_key) {
+ TemporaryFile corrupted_key;
+ std::string content;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("testkey_v1.x509.pem"), &content));
+ content = "random-contents" + content;
+ ASSERT_TRUE(android::base::WriteStringToFd(content, corrupted_key.release()));
+
+ TemporaryFile otacerts;
+ BuildCertificateArchive({ from_testdata_base("testkey_v2.x509.pem"), corrupted_key.path },
+ otacerts.release());
+ std::vector<Certificate> certs = LoadKeysFromZipfile(otacerts.path);
+ ASSERT_EQ(0, certs.size());
+}
+
+TEST(VerifierTest, LoadKeysFromZipfile_multiple_key) {
+ TemporaryFile otacerts;
+ BuildCertificateArchive(
+ {
+ from_testdata_base("testkey_v3.x509.pem"),
+ from_testdata_base("testkey_v4.x509.pem"),
+ from_testdata_base("testkey_v5.x509.pem"),
+
+ },
+ otacerts.release());
+ std::vector<Certificate> certs = LoadKeysFromZipfile(otacerts.path);
+ ASSERT_EQ(3, certs.size());
+
+ VerifyPackageWithCertificates("otasigned_v3.zip", certs);
+ VerifyPackageWithCertificates("otasigned_v4.zip", certs);
+ VerifyPackageWithCertificates("otasigned_v5.zip", certs);
+}
+
+class VerifierTest : public testing::TestWithParam<std::vector<std::string>> {
+ protected:
+ void SetUp() override {
+ std::vector<std::string> args = GetParam();
+ std::string path = from_testdata_base(args[0]);
+ memory_package_ = Package::CreateMemoryPackage(path, nullptr);
+ ASSERT_NE(nullptr, memory_package_);
+ file_package_ = Package::CreateFilePackage(path, nullptr);
+ ASSERT_NE(nullptr, file_package_);
+
+ for (auto it = ++args.cbegin(); it != args.cend(); ++it) {
+ std::string public_key_file = from_testdata_base("testkey_" + *it + ".x509.pem");
+ certs_.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(public_key_file, &certs_.back());
+ }
+ }
+
+ std::unique_ptr<Package> memory_package_;
+ std::unique_ptr<Package> file_package_;
+ std::vector<Certificate> certs_;
+};
+
+class VerifierSuccessTest : public VerifierTest {
+};
+
+class VerifierFailureTest : public VerifierTest {
+};
+
+TEST(VerifierTest, BadPackage_AlteredFooter) {
+ std::vector<Certificate> certs;
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
+
+ std::string package;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
+ ASSERT_EQ(std::string("\xc0\x06\xff\xff\xd2\x06", 6), package.substr(package.size() - 6, 6));
+
+ // Alter the footer.
+ package[package.size() - 5] = '\x05';
+ VerifyFile(package, certs, VERIFY_FAILURE);
+}
+
+TEST(VerifierTest, BadPackage_AlteredContent) {
+ std::vector<Certificate> certs;
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
+
+ std::string package;
+ ASSERT_TRUE(android::base::ReadFileToString(from_testdata_base("otasigned_v3.zip"), &package));
+ ASSERT_GT(package.size(), static_cast<size_t>(100));
+
+ // Alter the content.
+ std::string altered1(package);
+ altered1[50] += 1;
+ VerifyFile(altered1, certs, VERIFY_FAILURE);
+
+ std::string altered2(package);
+ altered2[10] += 1;
+ VerifyFile(altered2, certs, VERIFY_FAILURE);
+}
+
+TEST(VerifierTest, BadPackage_SignatureStartOutOfBounds) {
+ std::vector<Certificate> certs;
+ certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr);
+ LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &certs.back());
+
+ // 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;
+ VerifyFile(package, certs, VERIFY_FAILURE);
+}
+
+TEST_P(VerifierSuccessTest, VerifySucceed) {
+ ASSERT_EQ(VERIFY_SUCCESS, verify_file(memory_package_.get(), certs_));
+ ASSERT_EQ(VERIFY_SUCCESS, verify_file(file_package_.get(), certs_));
+}
+
+TEST_P(VerifierFailureTest, VerifyFailure) {
+ ASSERT_EQ(VERIFY_FAILURE, verify_file(memory_package_.get(), certs_));
+ ASSERT_EQ(VERIFY_FAILURE, verify_file(file_package_.get(), certs_));
+}
+
+INSTANTIATE_TEST_CASE_P(SingleKeySuccess, VerifierSuccessTest,
+ ::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_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_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_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", "v1"}),
+ std::vector<std::string>({"fake-eocd.zip", "v1"})));
diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp
index 827668521..0753d64e1 100644
--- a/tests/unit/zip_test.cpp
+++ b/tests/unit/zip_test.cpp
@@ -21,12 +21,11 @@
#include <vector>
#include <android-base/file.h>
-#include <android-base/test_utils.h>
#include <gtest/gtest.h>
-#include <otautil/SysUtil.h>
#include <ziparchive/zip_archive.h>
#include "common/test_constants.h"
+#include "otautil/sysutil.h"
TEST(ZipTest, OpenFromMemory) {
std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");
@@ -38,10 +37,9 @@ TEST(ZipTest, OpenFromMemory) {
ASSERT_EQ(0, OpenArchiveFromMemory(map.addr, map.length, zip_path.c_str(), &handle));
static constexpr const char* BINARY_PATH = "META-INF/com/google/android/update-binary";
- ZipString binary_path(BINARY_PATH);
ZipEntry binary_entry;
// Make sure the package opens correctly and its entry can be read.
- ASSERT_EQ(0, FindEntry(handle, binary_path, &binary_entry));
+ ASSERT_EQ(0, FindEntry(handle, BINARY_PATH, &binary_entry));
TemporaryFile tmp_binary;
ASSERT_NE(-1, tmp_binary.fd);