diff options
47 files changed, 1804 insertions, 1508 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 108429193..28aa06f45 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -7,5 +7,4 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} - -fw updater_sample/ - + --file_whitelist tools/ updater_sample/ diff --git a/install.cpp b/install.cpp index 42d264157..680937dd1 100644 --- a/install.cpp +++ b/install.cpp @@ -32,9 +32,7 @@ #include <condition_variable> #include <functional> #include <limits> -#include <map> #include <mutex> -#include <string> #include <thread> #include <vector> @@ -47,7 +45,6 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <vintf/VintfObjectRecovery.h> -#include <ziparchive/zip_archive.h> #include "common.h" #include "otautil/error_code.h" @@ -67,18 +64,7 @@ static constexpr float VERIFICATION_PROGRESS_FRACTION = 0.25; static std::condition_variable finish_log_temperature; -// This function parses and returns the build.version.incremental -static std::string parse_build_number(const std::string& str) { - size_t pos = str.find('='); - if (pos != std::string::npos) { - return android::base::Trim(str.substr(pos+1)); - } - - LOG(ERROR) << "Failed to parse build number in " << str; - return ""; -} - -bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) { +bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata) { CHECK(metadata != nullptr); static constexpr const char* METADATA_PATH = "META-INF/com/android/metadata"; @@ -90,101 +76,79 @@ bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata) { } uint32_t length = entry.uncompressed_length; - metadata->resize(length, '\0'); - int32_t err = ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&(*metadata)[0]), length); + std::string metadata_string(length, '\0'); + int32_t err = + ExtractToMemory(zip, &entry, reinterpret_cast<uint8_t*>(&metadata_string[0]), length); if (err != 0) { LOG(ERROR) << "Failed to extract " << METADATA_PATH << ": " << ErrorCodeString(err); return false; } - return true; -} -// Read the build.version.incremental of src/tgt from the metadata and log it to last_install. -static void read_source_target_build(ZipArchiveHandle zip, std::vector<std::string>* log_buffer) { - std::string metadata; - if (!read_metadata_from_package(zip, &metadata)) { - return; - } - // Examples of the pre-build and post-build strings in metadata: - // pre-build-incremental=2943039 - // post-build-incremental=2951741 - std::vector<std::string> lines = android::base::Split(metadata, "\n"); - for (const std::string& line : lines) { - std::string str = android::base::Trim(line); - if (android::base::StartsWith(str, "pre-build-incremental")) { - std::string source_build = parse_build_number(str); - if (!source_build.empty()) { - log_buffer->push_back("source_build: " + source_build); - } - } else if (android::base::StartsWith(str, "post-build-incremental")) { - std::string target_build = parse_build_number(str); - if (!target_build.empty()) { - log_buffer->push_back("target_build: " + target_build); - } - } - } -} - -// Parses the metadata of the OTA package in |zip| and checks whether we are allowed to accept this -// A/B package. Downgrading is not allowed unless explicitly enabled in the package and only for -// incremental packages. -static int check_newer_ab_build(ZipArchiveHandle zip) { - std::string metadata_str; - if (!read_metadata_from_package(zip, &metadata_str)) { - return INSTALL_CORRUPT; - } - std::map<std::string, std::string> metadata; - for (const std::string& line : android::base::Split(metadata_str, "\n")) { + for (const std::string& line : android::base::Split(metadata_string, "\n")) { size_t eq = line.find('='); if (eq != std::string::npos) { - metadata[line.substr(0, eq)] = line.substr(eq + 1); + metadata->emplace(android::base::Trim(line.substr(0, eq)), + android::base::Trim(line.substr(eq + 1))); } } - std::string value = android::base::GetProperty("ro.product.device", ""); - const std::string& pkg_device = metadata["pre-device"]; - if (pkg_device != value || pkg_device.empty()) { - LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << value; - return INSTALL_ERROR; + return true; +} + +// Gets the value for the given key in |metadata|. Returns an emtpy string if the key isn't +// present. +static std::string get_value(const std::map<std::string, std::string>& metadata, + const std::string& key) { + const auto& it = metadata.find(key); + return (it == metadata.end()) ? "" : it->second; +} + +static std::string OtaTypeToString(OtaType type) { + switch (type) { + case OtaType::AB: + return "AB"; + case OtaType::BLOCK: + return "BLOCK"; + case OtaType::BRICK: + return "BRICK"; } +} - // We allow the package to not have any serialno; and we also allow it to carry multiple serial - // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the - // verification if the device's serialno doesn't match any of these carried numbers. - value = android::base::GetProperty("ro.serialno", ""); - const std::string& pkg_serial_no = metadata["serialno"]; - if (!pkg_serial_no.empty()) { - bool match = false; - for (const std::string& number : android::base::Split(pkg_serial_no, "|")) { - if (value == android::base::Trim(number)) { - match = true; - break; - } - } - if (!match) { - LOG(ERROR) << "Package is for serial " << pkg_serial_no; - return INSTALL_ERROR; - } +// Read the build.version.incremental of src/tgt from the metadata and log it to last_install. +static void ReadSourceTargetBuild(const std::map<std::string, std::string>& metadata, + std::vector<std::string>* log_buffer) { + // Examples of the pre-build and post-build strings in metadata: + // pre-build-incremental=2943039 + // post-build-incremental=2951741 + auto source_build = get_value(metadata, "pre-build-incremental"); + if (!source_build.empty()) { + log_buffer->push_back("source_build: " + source_build); } - if (metadata["ota-type"] != "AB") { - LOG(ERROR) << "Package is not A/B"; - return INSTALL_ERROR; + auto target_build = get_value(metadata, "post-build-incremental"); + if (!target_build.empty()) { + log_buffer->push_back("target_build: " + target_build); } +} +// Checks the build version, fingerprint and timestamp in the metadata of the A/B package. +// Downgrading is not allowed unless explicitly enabled in the package and only for +// incremental packages. +static int CheckAbSpecificMetadata(const std::map<std::string, std::string>& metadata) { // Incremental updates should match the current build. - value = android::base::GetProperty("ro.build.version.incremental", ""); - const std::string& pkg_pre_build = metadata["pre-build-incremental"]; - if (!pkg_pre_build.empty() && pkg_pre_build != value) { - LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " << value; + auto device_pre_build = android::base::GetProperty("ro.build.version.incremental", ""); + auto pkg_pre_build = get_value(metadata, "pre-build-incremental"); + if (!pkg_pre_build.empty() && pkg_pre_build != device_pre_build) { + LOG(ERROR) << "Package is for source build " << pkg_pre_build << " but expected " + << device_pre_build; return INSTALL_ERROR; } - value = android::base::GetProperty("ro.build.fingerprint", ""); - const std::string& pkg_pre_build_fingerprint = metadata["pre-build"]; - if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != value) { + auto device_fingerprint = android::base::GetProperty("ro.build.fingerprint", ""); + auto pkg_pre_build_fingerprint = get_value(metadata, "pre-build"); + if (!pkg_pre_build_fingerprint.empty() && pkg_pre_build_fingerprint != device_fingerprint) { LOG(ERROR) << "Package is for source build " << pkg_pre_build_fingerprint << " but expected " - << value; + << device_fingerprint; return INSTALL_ERROR; } @@ -194,10 +158,11 @@ static int check_newer_ab_build(ZipArchiveHandle zip) { int64_t pkg_post_timestamp = 0; // We allow to full update to the same version we are running, in case there // is a problem with the current copy of that version. - if (metadata["post-timestamp"].empty() || - !android::base::ParseInt(metadata["post-timestamp"].c_str(), &pkg_post_timestamp) || + auto pkg_post_timestamp_string = get_value(metadata, "post-timestamp"); + if (pkg_post_timestamp_string.empty() || + !android::base::ParseInt(pkg_post_timestamp_string, &pkg_post_timestamp) || pkg_post_timestamp < build_timestamp) { - if (metadata["ota-downgrade"] != "yes") { + if (get_value(metadata, "ota-downgrade") != "yes") { LOG(ERROR) << "Update package is older than the current build, expected a build " "newer than timestamp " << build_timestamp << " but package has timestamp " << pkg_post_timestamp @@ -213,13 +178,55 @@ static int check_newer_ab_build(ZipArchiveHandle zip) { return 0; } +int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type) { + auto package_ota_type = get_value(metadata, "ota-type"); + auto expected_ota_type = OtaTypeToString(ota_type); + if (ota_type != OtaType::AB && ota_type != OtaType::BRICK) { + LOG(INFO) << "Skip package metadata check for ota type " << expected_ota_type; + return 0; + } + + if (package_ota_type != expected_ota_type) { + LOG(ERROR) << "Unexpected ota package type, expects " << expected_ota_type << ", actual " + << package_ota_type; + return INSTALL_ERROR; + } + + auto device = android::base::GetProperty("ro.product.device", ""); + auto pkg_device = get_value(metadata, "pre-device"); + if (pkg_device != device || pkg_device.empty()) { + LOG(ERROR) << "Package is for product " << pkg_device << " but expected " << device; + return INSTALL_ERROR; + } + + // We allow the package to not have any serialno; and we also allow it to carry multiple serial + // numbers split by "|"; e.g. serialno=serialno1|serialno2|serialno3 ... We will fail the + // verification if the device's serialno doesn't match any of these carried numbers. + auto pkg_serial_no = get_value(metadata, "serialno"); + if (!pkg_serial_no.empty()) { + auto device_serial_no = android::base::GetProperty("ro.serialno", ""); + bool serial_number_match = false; + for (const auto& number : android::base::Split(pkg_serial_no, "|")) { + if (device_serial_no == android::base::Trim(number)) { + serial_number_match = true; + } + } + if (!serial_number_match) { + LOG(ERROR) << "Package is for serial " << pkg_serial_no; + return INSTALL_ERROR; + } + } + + if (ota_type == OtaType::AB) { + return CheckAbSpecificMetadata(metadata); + } + + return 0; +} + int SetUpAbUpdateCommands(const std::string& package, ZipArchiveHandle zip, int status_fd, std::vector<std::string>* cmd) { CHECK(cmd != nullptr); - int ret = check_newer_ab_build(zip); - if (ret != 0) { - return ret; - } // For A/B updates we extract the payload properties to a buffer and obtain the RAW payload offset // in the zip file. @@ -311,20 +318,33 @@ static void log_max_temperature(int* max_temperature, const std::atomic<bool>& l static int try_update_binary(const std::string& package, ZipArchiveHandle zip, bool* wipe_cache, std::vector<std::string>* log_buffer, int retry_count, int* max_temperature) { - read_source_target_build(zip, log_buffer); + std::map<std::string, std::string> metadata; + if (!ReadMetadataFromPackage(zip, &metadata)) { + LOG(ERROR) << "Failed to parse metadata in the zip file"; + return INSTALL_CORRUPT; + } + + bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false); + // Verifies against the metadata in the package first. + if (int check_status = is_ab ? CheckPackageMetadata(metadata, OtaType::AB) : 0; + check_status != 0) { + log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); + return check_status; + } + + ReadSourceTargetBuild(metadata, log_buffer); int pipefd[2]; pipe(pipefd); - - bool is_ab = android::base::GetBoolProperty("ro.build.ab_update", false); std::vector<std::string> args; - int ret = is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args) - : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args); - if (ret) { + if (int update_status = + is_ab ? SetUpAbUpdateCommands(package, zip, pipefd[1], &args) + : SetUpNonAbUpdateCommands(package, zip, retry_count, pipefd[1], &args); + update_status != 0) { close(pipefd[0]); close(pipefd[1]); log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure)); - return ret; + return update_status; } // When executing the update binary contained in the package, the @@ -477,9 +497,16 @@ static int try_update_binary(const std::string& package, ZipArchiveHandle zip, b if (retry_update) { return INSTALL_RETRY; } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - LOG(ERROR) << "Error in " << package << " (Status " << WEXITSTATUS(status) << ")"; + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + LOG(ERROR) << "Error in " << package << " (status " << WEXITSTATUS(status) << ")"; + return INSTALL_ERROR; + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << "Error in " << package << " (killed by signal " << WTERMSIG(status) << ")"; return INSTALL_ERROR; + } else { + LOG(FATAL) << "Invalid status code " << status; } return INSTALL_SUCCESS; @@ -19,6 +19,7 @@ #include <stddef.h> +#include <map> #include <string> #include <ziparchive/zip_archive.h> @@ -33,6 +34,12 @@ enum InstallResult { INSTALL_KEY_INTERRUPTED }; +enum class OtaType { + AB, + BLOCK, + BRICK, +}; + // Installs the given update package. If INSTALL_SUCCESS is returned and *wipe_cache is true on // exit, caller should wipe the cache partition. int install_package(const std::string& package, bool* wipe_cache, bool needs_mount, @@ -42,12 +49,17 @@ int install_package(const std::string& package, bool* wipe_cache, bool needs_mou // otherwise return false. bool verify_package(const unsigned char* package_data, size_t package_size); -// Read meta data file of the package, write its content in the string pointed by meta_data. -// Return true if succeed, otherwise return false. -bool read_metadata_from_package(ZipArchiveHandle zip, std::string* metadata); +// Reads meta data file of the package; parses each line in the format "key=value"; and writes the +// result to |metadata|. Return true if succeed, otherwise return false. +bool ReadMetadataFromPackage(ZipArchiveHandle zip, std::map<std::string, std::string>* metadata); // Verifies the compatibility info in a Treble-compatible package. Returns true directly if the // entry doesn't exist. bool verify_package_compatibility(ZipArchiveHandle package_zip); +// Checks if the the metadata in the OTA package has expected values. Returns 0 on success. +// Mandatory checks: ota-type, pre-device and serial number(if presents) +// AB OTA specific checks: pre-build version, fingerprint, timestamp. +int CheckPackageMetadata(const std::map<std::string, std::string>& metadata, OtaType ota_type); + #endif // RECOVERY_INSTALL_H_ diff --git a/minadbd/Android.bp b/minadbd/Android.bp index 370232b3f..7e33261eb 100644 --- a/minadbd/Android.bp +++ b/minadbd/Android.bp @@ -15,6 +15,7 @@ cc_defaults { name: "minadbd_defaults", + cpp_std: "gnu++17", cflags: [ "-DADB_HOST=0", "-Wall", diff --git a/minui/graphics_adf.cpp b/minui/graphics_adf.cpp index 6fc193f74..9eea497d6 100644 --- a/minui/graphics_adf.cpp +++ b/minui/graphics_adf.cpp @@ -20,6 +20,7 @@ #include <fcntl.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/mman.h> #include <unistd.h> @@ -28,51 +29,60 @@ #include "minui/minui.h" -MinuiBackendAdf::MinuiBackendAdf() - : intf_fd(-1), dev(), current_surface(0), n_surfaces(0), surfaces() {} +GRSurfaceAdf::~GRSurfaceAdf() { + if (mmapped_buffer_) { + munmap(mmapped_buffer_, pitch * height); + } + if (fence_fd != -1) { + close(fence_fd); + } + if (fd != -1) { + close(fd); + } +} -int MinuiBackendAdf::SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf) { - *surf = {}; - surf->fence_fd = -1; - surf->fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format, - &surf->offset, &surf->pitch); - if (surf->fd < 0) { - return surf->fd; +std::unique_ptr<GRSurfaceAdf> GRSurfaceAdf::Create(int intf_fd, const drm_mode_modeinfo* mode, + __u32 format, int* err) { + __u32 offset; + __u32 pitch; + auto fd = adf_interface_simple_buffer_alloc(intf_fd, mode->hdisplay, mode->vdisplay, format, + &offset, &pitch); + + if (fd < 0) { + *err = fd; + return nullptr; } - surf->width = mode->hdisplay; - surf->height = mode->vdisplay; - surf->row_bytes = surf->pitch; - surf->pixel_bytes = (format == DRM_FORMAT_RGB565) ? 2 : 4; + std::unique_ptr<GRSurfaceAdf> surf = std::unique_ptr<GRSurfaceAdf>( + new GRSurfaceAdf(mode->hdisplay, mode->vdisplay, pitch, (format == DRM_FORMAT_RGB565 ? 2 : 4), + offset, pitch, fd)); auto mmapped = mmap(nullptr, surf->pitch * surf->height, PROT_WRITE, MAP_SHARED, surf->fd, surf->offset); if (mmapped == MAP_FAILED) { - int saved_errno = errno; - close(surf->fd); - return -saved_errno; + *err = -errno; + return nullptr; } surf->mmapped_buffer_ = static_cast<uint8_t*>(mmapped); - return 0; + return surf; } +MinuiBackendAdf::MinuiBackendAdf() : intf_fd(-1), dev(), current_surface(0), n_surfaces(0) {} + int MinuiBackendAdf::InterfaceInit() { adf_interface_data intf_data; - int err = adf_get_interface_data(intf_fd, &intf_data); - if (err < 0) return err; + if (int err = adf_get_interface_data(intf_fd, &intf_data); err < 0) return err; - int ret = 0; - err = SurfaceInit(&intf_data.current_mode, &surfaces[0]); - if (err < 0) { - fprintf(stderr, "allocating surface 0 failed: %s\n", strerror(-err)); - ret = err; + int result = 0; + surfaces[0] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result); + if (!surfaces[0]) { + fprintf(stderr, "Failed to allocate surface 0: %s\n", strerror(-result)); goto done; } - err = SurfaceInit(&intf_data.current_mode, &surfaces[1]); - if (err < 0) { - fprintf(stderr, "allocating surface 1 failed: %s\n", strerror(-err)); - surfaces[1] = {}; + surfaces[1] = GRSurfaceAdf::Create(intf_fd, &intf_data.current_mode, format, &result); + if (!surfaces[1]) { + fprintf(stderr, "Failed to allocate surface 1: %s\n", strerror(-result)); n_surfaces = 1; } else { n_surfaces = 2; @@ -80,7 +90,7 @@ int MinuiBackendAdf::InterfaceInit() { done: adf_free_interface_data(&intf_data); - return ret; + return result; } int MinuiBackendAdf::DeviceInit(adf_device* dev) { @@ -153,12 +163,12 @@ GRSurface* MinuiBackendAdf::Init() { } void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) { - static constexpr unsigned int warningTimeout = 3000; + static constexpr unsigned int kWarningTimeout = 3000; if (surf == nullptr) return; if (surf->fence_fd >= 0) { - int err = sync_wait(surf->fence_fd, warningTimeout); + int err = sync_wait(surf->fence_fd, kWarningTimeout); if (err < 0) { perror("adf sync fence wait error\n"); } @@ -169,33 +179,22 @@ void MinuiBackendAdf::Sync(GRSurfaceAdf* surf) { } GRSurface* MinuiBackendAdf::Flip() { - GRSurfaceAdf* surf = &surfaces[current_surface]; + const auto& surf = surfaces[current_surface]; int fence_fd = adf_interface_simple_post(intf_fd, eng_id, surf->width, surf->height, format, surf->fd, surf->offset, surf->pitch, -1); if (fence_fd >= 0) surf->fence_fd = fence_fd; current_surface = (current_surface + 1) % n_surfaces; - Sync(&surfaces[current_surface]); - return &surfaces[current_surface]; + Sync(surfaces[current_surface].get()); + return surfaces[current_surface].get(); } void MinuiBackendAdf::Blank(bool blank) { adf_interface_blank(intf_fd, blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON); } -void MinuiBackendAdf::SurfaceDestroy(GRSurfaceAdf* surf) { - if (surf->mmapped_buffer_) { - munmap(surf->mmapped_buffer_, surf->pitch * surf->height); - } - close(surf->fence_fd); - close(surf->fd); -} - MinuiBackendAdf::~MinuiBackendAdf() { adf_device_close(&dev); - for (unsigned int i = 0; i < n_surfaces; i++) { - SurfaceDestroy(&surfaces[i]); - } if (intf_fd >= 0) close(intf_fd); } diff --git a/minui/graphics_adf.h b/minui/graphics_adf.h index 099d32962..bf9842878 100644 --- a/minui/graphics_adf.h +++ b/minui/graphics_adf.h @@ -17,6 +17,9 @@ #pragma once #include <stdint.h> +#include <sys/types.h> + +#include <memory> #include <adf/adf.h> @@ -25,6 +28,11 @@ class GRSurfaceAdf : public GRSurface { public: + ~GRSurfaceAdf() override; + + static std::unique_ptr<GRSurfaceAdf> Create(int intf_fd, const drm_mode_modeinfo* mode, + __u32 format, int* err); + uint8_t* data() override { return mmapped_buffer_; } @@ -32,34 +40,36 @@ class GRSurfaceAdf : public GRSurface { private: friend class MinuiBackendAdf; - int fence_fd; - int fd; - __u32 offset; - __u32 pitch; + GRSurfaceAdf(int width, int height, int row_bytes, int pixel_bytes, __u32 offset, __u32 pitch, + int fd) + : GRSurface(width, height, row_bytes, pixel_bytes), offset(offset), pitch(pitch), fd(fd) {} + + const __u32 offset; + const __u32 pitch; + int fd; + int fence_fd{ -1 }; uint8_t* mmapped_buffer_{ nullptr }; }; class MinuiBackendAdf : public MinuiBackend { public: + MinuiBackendAdf(); + ~MinuiBackendAdf() override; GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendAdf() override; - MinuiBackendAdf(); private: - int SurfaceInit(const drm_mode_modeinfo* mode, GRSurfaceAdf* surf); int InterfaceInit(); int DeviceInit(adf_device* dev); - void SurfaceDestroy(GRSurfaceAdf* surf); void Sync(GRSurfaceAdf* surf); int intf_fd; adf_id_t eng_id; __u32 format; adf_device dev; - unsigned int current_surface; - unsigned int n_surfaces; - GRSurfaceAdf surfaces[2]; + size_t current_surface; + size_t n_surfaces; + std::unique_ptr<GRSurfaceAdf> surfaces[2]; }; diff --git a/minui/graphics_drm.cpp b/minui/graphics_drm.cpp index 81b49fd95..765e2625a 100644 --- a/minui/graphics_drm.cpp +++ b/minui/graphics_drm.cpp @@ -24,74 +24,37 @@ #include <sys/types.h> #include <unistd.h> +#include <memory> + +#include <android-base/macros.h> +#include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <drm_fourcc.h> #include <xf86drm.h> #include <xf86drmMode.h> #include "minui/minui.h" -#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A))) - -MinuiBackendDrm::MinuiBackendDrm() - : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {} - -void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) { - if (crtc) { - drmModeSetCrtc(drm_fd, crtc->crtc_id, - 0, // fb_id - 0, 0, // x,y - nullptr, // connectors - 0, // connector_count - nullptr); // mode - } -} - -int MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) { - int ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y - &main_monitor_connector->connector_id, - 1, // connector_count - &main_monitor_crtc->mode); - - if (ret) { - printf("drmModeSetCrtc failed ret=%d\n", ret); +GRSurfaceDrm::~GRSurfaceDrm() { + if (mmapped_buffer_) { + munmap(mmapped_buffer_, row_bytes * height); } - return ret; -} - -void MinuiBackendDrm::Blank(bool blank) { - if (blank) { - DrmDisableCrtc(drm_fd, main_monitor_crtc); - } else { - DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]); - } -} - -void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) { - if (!surface) return; - - if (surface->mmapped_buffer_) { - munmap(surface->mmapped_buffer_, surface->row_bytes * surface->height); - } - - if (surface->fb_id) { - int ret = drmModeRmFB(drm_fd, surface->fb_id); - if (ret) { - printf("drmModeRmFB failed ret=%d\n", ret); + if (fb_id) { + if (drmModeRmFB(drm_fd_, fb_id) != 0) { + perror("Failed to drmModeRmFB"); + // Falling through to free other resources. } } - if (surface->handle) { + if (handle) { drm_gem_close gem_close = {}; - gem_close.handle = surface->handle; + gem_close.handle = handle; - int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close); - if (ret) { - printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret); + if (drmIoctl(drm_fd_, DRM_IOCTL_GEM_CLOSE, &gem_close) != 0) { + perror("Failed to DRM_IOCTL_GEM_CLOSE"); } } - - delete surface; } static int drm_format_to_bpp(uint32_t format) { @@ -111,10 +74,7 @@ static int drm_format_to_bpp(uint32_t format) { } } -GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) { - GRSurfaceDrm* surface = new GRSurfaceDrm; - *surface = {}; - +std::unique_ptr<GRSurfaceDrm> GRSurfaceDrm::Create(int drm_fd, int width, int height) { uint32_t format; PixelFormat pixel_format = gr_pixel_format(); // PixelFormat comes in byte order, whereas DRM_FORMAT_* uses little-endian @@ -137,52 +97,74 @@ GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) { create_dumb.bpp = drm_format_to_bpp(format); create_dumb.flags = 0; - int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb) != 0) { + perror("Failed to DRM_IOCTL_MODE_CREATE_DUMB"); return nullptr; } - surface->handle = create_dumb.handle; + + // Cannot use std::make_unique to access non-public ctor. + auto surface = std::unique_ptr<GRSurfaceDrm>(new GRSurfaceDrm( + width, height, create_dumb.pitch, create_dumb.bpp / 8, drm_fd, create_dumb.handle)); uint32_t handles[4], pitches[4], offsets[4]; handles[0] = surface->handle; pitches[0] = create_dumb.pitch; offsets[0] = 0; - - ret = - drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0); - if (ret) { - printf("drmModeAddFB2 failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &surface->fb_id, 0) != + 0) { + perror("Failed to drmModeAddFB2"); return nullptr; } drm_mode_map_dumb map_dumb = {}; map_dumb.handle = create_dumb.handle; - ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); - if (ret) { - printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret); - DrmDestroySurface(surface); + if (drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb) != 0) { + perror("Failed to DRM_IOCTL_MODE_MAP_DUMB"); return nullptr; } - surface->height = height; - surface->width = width; - surface->row_bytes = create_dumb.pitch; - surface->pixel_bytes = create_dumb.bpp / 8; auto mmapped = mmap(nullptr, surface->height * surface->row_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map_dumb.offset); if (mmapped == MAP_FAILED) { - perror("mmap() failed"); - DrmDestroySurface(surface); + perror("Failed to mmap()"); return nullptr; } surface->mmapped_buffer_ = static_cast<uint8_t*>(mmapped); return surface; } +void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) { + if (crtc) { + drmModeSetCrtc(drm_fd, crtc->crtc_id, + 0, // fb_id + 0, 0, // x,y + nullptr, // connectors + 0, // connector_count + nullptr); // mode + } +} + +bool MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, + const std::unique_ptr<GRSurfaceDrm>& surface) { + if (drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0, // x,y + &main_monitor_connector->connector_id, + 1, // connector_count + &main_monitor_crtc->mode) != 0) { + perror("Failed to drmModeSetCrtc"); + return false; + } + return true; +} + +void MinuiBackendDrm::Blank(bool blank) { + if (blank) { + DrmDisableCrtc(drm_fd, main_monitor_crtc); + } else { + DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]); + } +} + static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources, drmModeConnector* connector) { // Find the encoder. If we already have one, just use it. @@ -264,7 +246,7 @@ drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources do { main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]); i++; - } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority)); + } while (!main_monitor_connector && i < arraysize(kConnectorPriority)); /* If we didn't find a connector, grab the first one that is connected. */ if (!main_monitor_connector) { @@ -298,60 +280,53 @@ void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmMode GRSurface* MinuiBackendDrm::Init() { drmModeRes* res = nullptr; + drm_fd = -1; /* Consider DRM devices in order. */ for (int i = 0; i < DRM_MAX_MINOR; i++) { - char* dev_name; - int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i); - if (ret < 0) continue; + auto dev_name = android::base::StringPrintf(DRM_DEV_NAME, DRM_DIR_NAME, i); + android::base::unique_fd fd(open(dev_name.c_str(), O_RDWR)); + if (fd == -1) continue; - drm_fd = open(dev_name, O_RDWR, 0); - free(dev_name); - if (drm_fd < 0) continue; - - uint64_t cap = 0; /* We need dumb buffers. */ - ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap); - if (ret || cap == 0) { - close(drm_fd); + if (uint64_t cap = 0; drmGetCap(fd.get(), DRM_CAP_DUMB_BUFFER, &cap) != 0 || cap == 0) { continue; } - res = drmModeGetResources(drm_fd); + res = drmModeGetResources(fd.get()); if (!res) { - close(drm_fd); continue; } /* Use this device if it has at least one connected monitor. */ if (res->count_crtcs > 0 && res->count_connectors > 0) { - if (find_first_connected_connector(drm_fd, res)) break; + if (find_first_connected_connector(fd.get(), res)) { + drm_fd = fd.release(); + break; + } } drmModeFreeResources(res); - close(drm_fd); res = nullptr; } - if (drm_fd < 0 || res == nullptr) { - perror("cannot find/open a drm device"); + if (drm_fd == -1 || res == nullptr) { + perror("Failed to find/open a drm device"); return nullptr; } uint32_t selected_mode; main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode); - if (!main_monitor_connector) { - printf("main_monitor_connector not found\n"); + fprintf(stderr, "Failed to find main_monitor_connector\n"); drmModeFreeResources(res); close(drm_fd); return nullptr; } main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector); - if (!main_monitor_crtc) { - printf("main_monitor_crtc not found\n"); + fprintf(stderr, "Failed to find main_monitor_crtc\n"); drmModeFreeResources(res); close(drm_fd); return nullptr; @@ -366,21 +341,20 @@ GRSurface* MinuiBackendDrm::Init() { drmModeFreeResources(res); - GRSurfaceDrms[0] = DrmCreateSurface(width, height); - GRSurfaceDrms[1] = DrmCreateSurface(width, height); + GRSurfaceDrms[0] = GRSurfaceDrm::Create(drm_fd, width, height); + GRSurfaceDrms[1] = GRSurfaceDrm::Create(drm_fd, width, height); if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) { - // GRSurfaceDrms and drm_fd should be freed in d'tor. return nullptr; } current_buffer = 0; // We will likely encounter errors in the backend functions (i.e. Flip) if EnableCrtc fails. - if (DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]) != 0) { + if (!DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1])) { return nullptr; } - return GRSurfaceDrms[0]; + return GRSurfaceDrms[0].get(); } static void page_flip_complete(__unused int fd, @@ -393,12 +367,9 @@ static void page_flip_complete(__unused int fd, GRSurface* MinuiBackendDrm::Flip() { bool ongoing_flip = true; - - int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, - GRSurfaceDrms[current_buffer]->fb_id, - DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip); - if (ret < 0) { - printf("drmModePageFlip failed ret=%d\n", ret); + if (drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id, GRSurfaceDrms[current_buffer]->fb_id, + DRM_MODE_PAGE_FLIP_EVENT, &ongoing_flip) != 0) { + perror("Failed to drmModePageFlip"); return nullptr; } @@ -408,9 +379,8 @@ GRSurface* MinuiBackendDrm::Flip() { .events = POLLIN }; - ret = poll(&fds, 1, -1); - if (ret == -1 || !(fds.revents & POLLIN)) { - printf("poll() failed on drm fd\n"); + if (poll(&fds, 1, -1) == -1 || !(fds.revents & POLLIN)) { + perror("Failed to poll() on drm fd"); break; } @@ -419,21 +389,18 @@ GRSurface* MinuiBackendDrm::Flip() { .page_flip_handler = page_flip_complete }; - ret = drmHandleEvent(drm_fd, &evctx); - if (ret != 0) { - printf("drmHandleEvent failed ret=%d\n", ret); + if (drmHandleEvent(drm_fd, &evctx) != 0) { + perror("Failed to drmHandleEvent"); break; } } current_buffer = 1 - current_buffer; - return GRSurfaceDrms[current_buffer]; + return GRSurfaceDrms[current_buffer].get(); } MinuiBackendDrm::~MinuiBackendDrm() { DrmDisableCrtc(drm_fd, main_monitor_crtc); - DrmDestroySurface(GRSurfaceDrms[0]); - DrmDestroySurface(GRSurfaceDrms[1]); drmModeFreeCrtc(main_monitor_crtc); drmModeFreeConnector(main_monitor_connector); close(drm_fd); diff --git a/minui/graphics_drm.h b/minui/graphics_drm.h index f3aad6bfc..6ba46e60b 100644 --- a/minui/graphics_drm.h +++ b/minui/graphics_drm.h @@ -18,6 +18,8 @@ #include <stdint.h> +#include <memory> + #include <xf86drmMode.h> #include "graphics.h" @@ -25,6 +27,11 @@ class GRSurfaceDrm : public GRSurface { public: + ~GRSurfaceDrm() override; + + // Creates a GRSurfaceDrm instance. + static std::unique_ptr<GRSurfaceDrm> Create(int drm_fd, int width, int height); + uint8_t* data() override { return mmapped_buffer_; } @@ -32,30 +39,34 @@ class GRSurfaceDrm : public GRSurface { private: friend class MinuiBackendDrm; - uint32_t fb_id; - uint32_t handle; + GRSurfaceDrm(int width, int height, int row_bytes, int pixel_bytes, int drm_fd, uint32_t handle) + : GRSurface(width, height, row_bytes, pixel_bytes), drm_fd_(drm_fd), handle(handle) {} + + const int drm_fd_; + + uint32_t fb_id{ 0 }; + uint32_t handle{ 0 }; uint8_t* mmapped_buffer_{ nullptr }; }; class MinuiBackendDrm : public MinuiBackend { public: + MinuiBackendDrm() = default; + ~MinuiBackendDrm() override; + GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendDrm() override; - MinuiBackendDrm(); private: void DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc); - int DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface); - GRSurfaceDrm* DrmCreateSurface(int width, int height); - void DrmDestroySurface(GRSurfaceDrm* surface); + bool DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, const std::unique_ptr<GRSurfaceDrm>& surface); void DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc); drmModeConnector* FindMainMonitor(int fd, drmModeRes* resources, uint32_t* mode_index); - GRSurfaceDrm* GRSurfaceDrms[2]; - int current_buffer; - drmModeCrtc* main_monitor_crtc; - drmModeConnector* main_monitor_connector; - int drm_fd; + std::unique_ptr<GRSurfaceDrm> GRSurfaceDrms[2]; + int current_buffer{ 0 }; + drmModeCrtc* main_monitor_crtc{ nullptr }; + drmModeConnector* main_monitor_connector{ nullptr }; + int drm_fd{ -1 }; }; diff --git a/minui/graphics_fbdev.cpp b/minui/graphics_fbdev.cpp index f958d62d4..93e4420d3 100644 --- a/minui/graphics_fbdev.cpp +++ b/minui/graphics_fbdev.cpp @@ -26,21 +26,29 @@ #include <sys/types.h> #include <unistd.h> +#include <memory> + +#include <android-base/unique_fd.h> + #include "minui/minui.h" -MinuiBackendFbdev::MinuiBackendFbdev() : gr_draw(nullptr), fb_fd(-1) {} +std::unique_ptr<GRSurfaceFbdev> GRSurfaceFbdev::Create(int width, int height, int row_bytes, + int pixel_bytes) { + // Cannot use std::make_unique to access non-public ctor. + return std::unique_ptr<GRSurfaceFbdev>(new GRSurfaceFbdev(width, height, row_bytes, pixel_bytes)); +} void MinuiBackendFbdev::Blank(bool blank) { int ret = ioctl(fb_fd, FBIOBLANK, blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK); if (ret < 0) perror("ioctl(): blank"); } -void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) { +void MinuiBackendFbdev::SetDisplayedFramebuffer(size_t n) { if (n > 1 || !double_buffered) return; - vi.yres_virtual = gr_framebuffer[0].height * 2; - vi.yoffset = n * gr_framebuffer[0].height; - vi.bits_per_pixel = gr_framebuffer[0].pixel_bytes * 8; + vi.yres_virtual = gr_framebuffer[0]->height * 2; + vi.yoffset = n * gr_framebuffer[0]->height; + vi.bits_per_pixel = gr_framebuffer[0]->pixel_bytes * 8; if (ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vi) < 0) { perror("active fb swap failed"); } @@ -48,7 +56,7 @@ void MinuiBackendFbdev::SetDisplayedFramebuffer(unsigned n) { } GRSurface* MinuiBackendFbdev::Init() { - int fd = open("/dev/graphics/fb0", O_RDWR); + android::base::unique_fd fd(open("/dev/graphics/fb0", O_RDWR)); if (fd == -1) { perror("cannot open fb0"); return nullptr; @@ -57,13 +65,11 @@ GRSurface* MinuiBackendFbdev::Init() { fb_fix_screeninfo fi; if (ioctl(fd, FBIOGET_FSCREENINFO, &fi) < 0) { perror("failed to get fb0 info"); - close(fd); return nullptr; } if (ioctl(fd, FBIOGET_VSCREENINFO, &vi) < 0) { perror("failed to get fb0 info"); - close(fd); return nullptr; } @@ -90,46 +96,41 @@ GRSurface* MinuiBackendFbdev::Init() { void* bits = mmap(0, fi.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (bits == MAP_FAILED) { perror("failed to mmap framebuffer"); - close(fd); return nullptr; } memset(bits, 0, fi.smem_len); - gr_framebuffer[0].width = vi.xres; - gr_framebuffer[0].height = vi.yres; - gr_framebuffer[0].row_bytes = fi.line_length; - gr_framebuffer[0].pixel_bytes = vi.bits_per_pixel / 8; - gr_framebuffer[0].buffer_ = static_cast<uint8_t*>(bits); - memset(gr_framebuffer[0].buffer_, 0, gr_framebuffer[0].height * gr_framebuffer[0].row_bytes); + gr_framebuffer[0] = + GRSurfaceFbdev::Create(vi.xres, vi.yres, fi.line_length, vi.bits_per_pixel / 8); + gr_framebuffer[0]->buffer_ = static_cast<uint8_t*>(bits); + memset(gr_framebuffer[0]->buffer_, 0, gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes); + + gr_framebuffer[1] = + GRSurfaceFbdev::Create(gr_framebuffer[0]->width, gr_framebuffer[0]->height, + gr_framebuffer[0]->row_bytes, gr_framebuffer[0]->pixel_bytes); /* check if we can use double buffering */ if (vi.yres * fi.line_length * 2 <= fi.smem_len) { double_buffered = true; - gr_framebuffer[1] = gr_framebuffer[0]; - gr_framebuffer[1].buffer_ = - gr_framebuffer[0].buffer_ + gr_framebuffer[0].height * gr_framebuffer[0].row_bytes; - - gr_draw = gr_framebuffer + 1; - + gr_framebuffer[1]->buffer_ = + gr_framebuffer[0]->buffer_ + gr_framebuffer[0]->height * gr_framebuffer[0]->row_bytes; } else { double_buffered = false; - // Without double-buffering, we allocate RAM for a buffer to - // draw in, and then "flipping" the buffer consists of a - // memcpy from the buffer we allocated to the framebuffer. - - gr_draw = new GRSurfaceFbdev; - *gr_draw = gr_framebuffer[0]; - gr_draw->buffer_ = new uint8_t[gr_draw->height * gr_draw->row_bytes]; + // Without double-buffering, we allocate RAM for a buffer to draw in, and then "flipping" the + // buffer consists of a memcpy from the buffer we allocated to the framebuffer. + memory_buffer.resize(gr_framebuffer[1]->height * gr_framebuffer[1]->row_bytes); + gr_framebuffer[1]->buffer_ = memory_buffer.data(); } + gr_draw = gr_framebuffer[1].get(); memset(gr_draw->buffer_, 0, gr_draw->height * gr_draw->row_bytes); - fb_fd = fd; + fb_fd = std::move(fd); SetDisplayedFramebuffer(0); - printf("framebuffer: %d (%d x %d)\n", fb_fd, gr_draw->width, gr_draw->height); + printf("framebuffer: %d (%d x %d)\n", fb_fd.get(), gr_draw->width, gr_draw->height); Blank(true); Blank(false); @@ -139,25 +140,13 @@ GRSurface* MinuiBackendFbdev::Init() { GRSurface* MinuiBackendFbdev::Flip() { if (double_buffered) { - // Change gr_draw to point to the buffer currently displayed, - // then flip the driver so we're displaying the other buffer - // instead. - gr_draw = gr_framebuffer + displayed_buffer; + // Change gr_draw to point to the buffer currently displayed, then flip the driver so we're + // displaying the other buffer instead. + gr_draw = gr_framebuffer[displayed_buffer].get(); SetDisplayedFramebuffer(1 - displayed_buffer); } else { // Copy from the in-memory surface to the framebuffer. - memcpy(gr_framebuffer[0].buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes); + memcpy(gr_framebuffer[0]->buffer_, gr_draw->buffer_, gr_draw->height * gr_draw->row_bytes); } return gr_draw; } - -MinuiBackendFbdev::~MinuiBackendFbdev() { - close(fb_fd); - fb_fd = -1; - - if (!double_buffered && gr_draw) { - delete[] gr_draw->buffer_; - delete gr_draw; - } - gr_draw = nullptr; -} diff --git a/minui/graphics_fbdev.h b/minui/graphics_fbdev.h index be813dccb..016ab88bc 100644 --- a/minui/graphics_fbdev.h +++ b/minui/graphics_fbdev.h @@ -19,37 +19,52 @@ #include <linux/fb.h> #include <stdint.h> +#include <memory> +#include <vector> + +#include <android-base/unique_fd.h> + #include "graphics.h" #include "minui/minui.h" class GRSurfaceFbdev : public GRSurface { public: + // Creates and returns a GRSurfaceFbdev instance, or nullptr on error. + static std::unique_ptr<GRSurfaceFbdev> Create(int width, int height, int row_bytes, + int pixel_bytes); + uint8_t* data() override { return buffer_; } + protected: + using GRSurface::GRSurface; + private: friend class MinuiBackendFbdev; // Points to the start of the buffer: either the mmap'd framebuffer or one allocated in-memory. - uint8_t* buffer_; + uint8_t* buffer_{ nullptr }; }; class MinuiBackendFbdev : public MinuiBackend { public: + MinuiBackendFbdev() = default; + ~MinuiBackendFbdev() override = default; + GRSurface* Init() override; GRSurface* Flip() override; void Blank(bool) override; - ~MinuiBackendFbdev() override; - MinuiBackendFbdev(); private: - void SetDisplayedFramebuffer(unsigned n); + void SetDisplayedFramebuffer(size_t n); - GRSurfaceFbdev gr_framebuffer[2]; + std::unique_ptr<GRSurfaceFbdev> gr_framebuffer[2]; + // Points to the current surface (i.e. one of the two gr_framebuffer's). + GRSurfaceFbdev* gr_draw{ nullptr }; bool double_buffered; - GRSurfaceFbdev* gr_draw; - int displayed_buffer; + std::vector<uint8_t> memory_buffer; + size_t displayed_buffer{ 0 }; fb_var_screeninfo vi; - int fb_fd; + android::base::unique_fd fb_fd; }; diff --git a/minui/include/minui/minui.h b/minui/include/minui/minui.h index e9bd1c4f1..3231248a0 100644 --- a/minui/include/minui/minui.h +++ b/minui/include/minui/minui.h @@ -17,6 +17,7 @@ #pragma once #include <stdint.h> +#include <stdlib.h> #include <sys/types.h> #include <functional> @@ -24,22 +25,27 @@ #include <string> #include <vector> +#include <android-base/macros.h> + // // Graphics. // class GRSurface { public: - GRSurface() = default; - virtual ~GRSurface(); + virtual ~GRSurface() = default; + + // Creates and returns a GRSurface instance that's sufficient for storing an image of the given + // size. The starting address of the surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns + // the created GRSurface instance (in std::unique_ptr), or nullptr on error. + static std::unique_ptr<GRSurface> Create(int width, int height, int row_bytes, int pixel_bytes, + size_t data_size); - // Creates and returns a GRSurface instance for the given data_size. The starting address of the - // surface data is aligned to SURFACE_DATA_ALIGNMENT. Returns the created GRSurface instance (in - // std::unique_ptr), or nullptr on error. - static std::unique_ptr<GRSurface> Create(size_t data_size); + // Clones the current GRSurface instance (i.e. an image). + std::unique_ptr<GRSurface> Clone() const; virtual uint8_t* data() { - return data_; + return data_.get(); } const uint8_t* data() const { @@ -51,8 +57,22 @@ class GRSurface { int row_bytes; int pixel_bytes; + protected: + GRSurface(int width, int height, int row_bytes, int pixel_bytes) + : width(width), height(height), row_bytes(row_bytes), pixel_bytes(pixel_bytes) {} + private: - uint8_t* data_{ nullptr }; + // The deleter for data_, whose data is allocated via aligned_alloc(3). + struct DataDeleter { + void operator()(uint8_t* data) { + free(data); + } + }; + + std::unique_ptr<uint8_t, DataDeleter> data_; + size_t data_size_; + + DISALLOW_COPY_AND_ASSIGN(GRSurface); }; struct GRFont { diff --git a/minui/resources.cpp b/minui/resources.cpp index c01c1868e..c7af1904d 100644 --- a/minui/resources.cpp +++ b/minui/resources.cpp @@ -39,21 +39,24 @@ static std::string g_resource_dir{ "/res/images" }; -std::unique_ptr<GRSurface> GRSurface::Create(size_t data_size) { +std::unique_ptr<GRSurface> GRSurface::Create(int width, int height, int row_bytes, int pixel_bytes, + size_t data_size) { static constexpr size_t kSurfaceDataAlignment = 8; - std::unique_ptr<GRSurface> result = std::make_unique<GRSurface>(); - size_t aligned_size = + // Cannot use std::make_unique to access non-public ctor. + auto result = std::unique_ptr<GRSurface>(new GRSurface(width, height, row_bytes, pixel_bytes)); + result->data_size_ = (data_size + kSurfaceDataAlignment - 1) / kSurfaceDataAlignment * kSurfaceDataAlignment; - result->data_ = static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, aligned_size)); - if (result->data_ == nullptr) return nullptr; + result->data_.reset( + static_cast<uint8_t*>(aligned_alloc(kSurfaceDataAlignment, result->data_size_))); + if (!result->data_) return nullptr; return result; } -GRSurface::~GRSurface() { - if (data_ != nullptr) { - free(data_); - data_ = nullptr; - } +std::unique_ptr<GRSurface> GRSurface::Clone() const { + auto result = GRSurface::Create(width, height, row_bytes, pixel_bytes, data_size_); + if (!result) return nullptr; + memcpy(result->data(), data(), data_size_); + return result; } PngHandler::PngHandler(const std::string& name) { @@ -68,7 +71,7 @@ PngHandler::PngHandler(const std::string& name) { return; } - unsigned char header[8]; + uint8_t header[8]; size_t bytesRead = fread(header, 1, sizeof(header), png_fp_.get()); if (bytesRead != sizeof(header)) { error_code_ = -2; @@ -131,70 +134,49 @@ PngHandler::~PngHandler() { } } -// "display" surfaces are transformed into the framebuffer's required -// pixel format (currently only RGBX is supported) at load time, so -// gr_blit() can be nothing more than a memcpy() for each row. The -// next two functions are the only ones that know anything about the -// framebuffer pixel format; they need to be modified if the -// framebuffer format changes (but nothing else should). - -// Allocates and returns a GRSurface* sufficient for storing an image of the indicated size in the -// framebuffer pixel format. -static std::unique_ptr<GRSurface> init_display_surface(png_uint_32 width, png_uint_32 height) { - std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height * 4); - if (!surface) return nullptr; - - surface->width = width; - surface->height = height; - surface->row_bytes = width * 4; - surface->pixel_bytes = 4; - - return surface; -} +// "display" surfaces are transformed into the framebuffer's required pixel format (currently only +// RGBX is supported) at load time, so gr_blit() can be nothing more than a memcpy() for each row. -// Copy 'input_row' to 'output_row', transforming it to the -// framebuffer pixel format. The input format depends on the value of -// 'channels': +// Copies 'input_row' to 'output_row', transforming it to the framebuffer pixel format. The input +// format depends on the value of 'channels': // // 1 - input is 8-bit grayscale // 3 - input is 24-bit RGB // 4 - input is 32-bit RGBA/RGBX // // 'width' is the number of pixels in the row. -static void transform_rgb_to_draw(unsigned char* input_row, - unsigned char* output_row, - int channels, int width) { - int x; - unsigned char* ip = input_row; - unsigned char* op = output_row; - - switch (channels) { - case 1: - // expand gray level to RGBX - for (x = 0; x < width; ++x) { - *op++ = *ip; - *op++ = *ip; - *op++ = *ip; - *op++ = 0xff; - ip++; - } - break; - - case 3: - // expand RGBA to RGBX - for (x = 0; x < width; ++x) { - *op++ = *ip++; - *op++ = *ip++; - *op++ = *ip++; - *op++ = 0xff; - } - break; - - case 4: - // copy RGBA to RGBX - memcpy(output_row, input_row, width*4); - break; - } +static void TransformRgbToDraw(const uint8_t* input_row, uint8_t* output_row, int channels, + int width) { + const uint8_t* ip = input_row; + uint8_t* op = output_row; + + switch (channels) { + case 1: + // expand gray level to RGBX + for (int x = 0; x < width; ++x) { + *op++ = *ip; + *op++ = *ip; + *op++ = *ip; + *op++ = 0xff; + ip++; + } + break; + + case 3: + // expand RGBA to RGBX + for (int x = 0; x < width; ++x) { + *op++ = *ip++; + *op++ = *ip++; + *op++ = *ip++; + *op++ = 0xff; + } + break; + + case 4: + // copy RGBA to RGBX + memcpy(output_row, input_row, width * 4); + break; + } } int res_create_display_surface(const char* name, GRSurface** pSurface) { @@ -207,7 +189,7 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) { png_uint_32 width = png_handler.width(); png_uint_32 height = png_handler.height(); - std::unique_ptr<GRSurface> surface = init_display_surface(width, height); + auto surface = GRSurface::Create(width, height, width * 4, 4, width * height * 4); if (!surface) { return -8; } @@ -218,10 +200,10 @@ int res_create_display_surface(const char* name, GRSurface** pSurface) { } for (png_uint_32 y = 0; y < height; ++y) { - std::vector<unsigned char> p_row(width * 4); + std::vector<uint8_t> p_row(width * 4); png_read_row(png_ptr, p_row.data(), nullptr); - transform_rgb_to_draw(p_row.data(), surface->data() + y * surface->row_bytes, - png_handler.channels(), width); + TransformRgbToDraw(p_row.data(), surface->data() + y * surface->row_bytes, + png_handler.channels(), width); } *pSurface = surface.release(); @@ -277,7 +259,9 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, goto exit; } for (int i = 0; i < *frames; ++i) { - auto created_surface = init_display_surface(width, height / *frames); + auto height_per_frame = height / *frames; + auto created_surface = + GRSurface::Create(width, height_per_frame, width * 4, 4, width * height_per_frame); if (!created_surface) { result = -8; goto exit; @@ -290,11 +274,11 @@ int res_create_multi_display_surface(const char* name, int* frames, int* fps, } for (png_uint_32 y = 0; y < height; ++y) { - std::vector<unsigned char> p_row(width * 4); + std::vector<uint8_t> p_row(width * 4); png_read_row(png_ptr, p_row.data(), nullptr); int frame = y % *frames; - unsigned char* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes; - transform_rgb_to_draw(p_row.data(), out_row, png_handler.channels(), width); + uint8_t* out_row = surface[frame]->data() + (y / *frames) * surface[frame]->row_bytes; + TransformRgbToDraw(p_row.data(), out_row, png_handler.channels(), width); } *pSurface = surface; @@ -325,14 +309,10 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) { png_uint_32 width = png_handler.width(); png_uint_32 height = png_handler.height(); - std::unique_ptr<GRSurface> surface = GRSurface::Create(width * height); + auto surface = GRSurface::Create(width, height, width, 1, width * height); if (!surface) { return -8; } - surface->width = width; - surface->height = height; - surface->row_bytes = width; - surface->pixel_bytes = 1; PixelFormat pixel_format = gr_pixel_format(); if (pixel_format == PixelFormat::ABGR || pixel_format == PixelFormat::BGRA) { @@ -340,7 +320,7 @@ int res_create_alpha_surface(const char* name, GRSurface** pSurface) { } for (png_uint_32 y = 0; y < height; ++y) { - unsigned char* p_row = surface->data() + y * surface->row_bytes; + uint8_t* p_row = surface->data() + y * surface->row_bytes; png_read_row(png_ptr, p_row, nullptr); } @@ -389,7 +369,7 @@ std::vector<std::string> get_locales_in_png(const std::string& png_name) { } std::vector<std::string> result; - std::vector<unsigned char> row(png_handler.width()); + std::vector<uint8_t> row(png_handler.width()); for (png_uint_32 y = 0; y < png_handler.height(); ++y) { png_read_row(png_handler.png_ptr(), row.data(), nullptr); int h = (row[3] << 8) | row[2]; @@ -425,7 +405,7 @@ int res_create_localized_alpha_surface(const char* name, png_uint_32 height = png_handler.height(); for (png_uint_32 y = 0; y < height; ++y) { - std::vector<unsigned char> row(width); + std::vector<uint8_t> row(width); png_read_row(png_ptr, row.data(), nullptr); int w = (row[1] << 8) | row[0]; int h = (row[3] << 8) | row[2]; @@ -435,14 +415,10 @@ int res_create_localized_alpha_surface(const char* name, if (y + 1 + h >= height || matches_locale(loc, locale)) { printf(" %20s: %s (%d x %d @ %d)\n", name, loc, w, h, y); - std::unique_ptr<GRSurface> surface = GRSurface::Create(w * h); + auto surface = GRSurface::Create(w, h, w, 1, w * h); if (!surface) { return -8; } - surface->width = w; - surface->height = h; - surface->row_bytes = w; - surface->pixel_bytes = 1; for (int i = 0; i < h; ++i, ++y) { png_read_row(png_ptr, row.data(), nullptr); diff --git a/recovery.cpp b/recovery.cpp index d7bc6fd2f..e17526a95 100644 --- a/recovery.cpp +++ b/recovery.cpp @@ -520,34 +520,17 @@ static bool check_wipe_package(size_t wipe_package_size) { LOG(ERROR) << "Can't open wipe package : " << ErrorCodeString(err); return false; } - std::string metadata; - if (!read_metadata_from_package(zip, &metadata)) { - CloseArchive(zip); - return false; + + std::map<std::string, std::string> metadata; + if (!ReadMetadataFromPackage(zip, &metadata)) { + LOG(ERROR) << "Failed to parse metadata in the zip file"; + return false; } + + int result = CheckPackageMetadata(metadata, OtaType::BRICK); CloseArchive(zip); - // Check metadata - std::vector<std::string> lines = android::base::Split(metadata, "\n"); - bool ota_type_matched = false; - bool device_type_matched = false; - bool has_serial_number = false; - bool serial_number_matched = false; - for (const auto& line : lines) { - if (line == "ota-type=BRICK") { - ota_type_matched = true; - } else if (android::base::StartsWith(line, "pre-device=")) { - std::string device_type = line.substr(strlen("pre-device=")); - std::string real_device_type = android::base::GetProperty("ro.build.product", ""); - device_type_matched = (device_type == real_device_type); - } else if (android::base::StartsWith(line, "serialno=")) { - std::string serial_no = line.substr(strlen("serialno=")); - std::string real_serial_no = android::base::GetProperty("ro.serialno", ""); - has_serial_number = true; - serial_number_matched = (serial_no == real_serial_no); - } - } - return ota_type_matched && device_type_matched && (!has_serial_number || serial_number_matched); + return result == 0; } // Wipes the current A/B device, with a secure wipe of all the partitions in RECOVERY_WIPE. diff --git a/recovery_main.cpp b/recovery_main.cpp index a95299ea7..6f5080238 100644 --- a/recovery_main.cpp +++ b/recovery_main.cpp @@ -478,8 +478,13 @@ int main(int argc, char** argv) { break; case Device::ENTER_FASTBOOT: - LOG(INFO) << "Entering fastboot"; - fastboot = true; + if (logical_partitions_mapped()) { + ui->Print("Partitions may be mounted - rebooting to enter fastboot."); + android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,fastboot"); + } else { + LOG(INFO) << "Entering fastboot"; + fastboot = true; + } break; case Device::ENTER_RECOVERY: @@ -38,10 +38,12 @@ #include <cryptfs.h> #include <ext4_utils/wipe.h> #include <fs_mgr.h> +#include <fs_mgr_dm_linear.h> #include "otautil/mounts.h" static struct fstab* fstab = nullptr; +static bool did_map_logical_partitions = false; extern struct selabel_handle* sehandle; @@ -117,6 +119,25 @@ int ensure_path_mounted_at(const char* path, const char* mount_point) { mount_point = v->mount_point; } + // If we can't acquire the block device for a logical partition, it likely + // was never created. In that case we try to create it. + if (fs_mgr_is_logical(v) && !fs_mgr_update_logical_partition(v)) { + if (did_map_logical_partitions) { + LOG(ERROR) << "Failed to find block device for partition"; + return -1; + } + std::string super_name = fs_mgr_get_super_partition_name(); + if (!android::fs_mgr::CreateLogicalPartitions(super_name)) { + LOG(ERROR) << "Failed to create logical partitions"; + return -1; + } + did_map_logical_partitions = true; + if (!fs_mgr_update_logical_partition(v)) { + LOG(ERROR) << "Failed to find block device for partition"; + return -1; + } + } + const MountedVolume* mv = find_mounted_volume_by_mount_point(mount_point); if (mv != nullptr) { // Volume is already mounted. @@ -387,3 +408,7 @@ int setup_install_mounts() { } return 0; } + +bool logical_partitions_mapped() { + return did_map_logical_partitions; +} @@ -53,4 +53,6 @@ int format_volume(const char* volume, const char* directory); // mounted (/tmp and /cache) are mounted. Returns 0 on success. int setup_install_mounts(); +bool logical_partitions_mapped(); + #endif // RECOVERY_ROOTS_H_ diff --git a/screen_ui.cpp b/screen_ui.cpp index 2db27d6a7..ed71888d1 100644 --- a/screen_ui.cpp +++ b/screen_ui.cpp @@ -197,11 +197,16 @@ int TextMenu::DrawItems(int x, int y, int screen_width, bool long_press) const { return offset; } -GraphicMenu::GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items, +GraphicMenu::GraphicMenu(const GRSurface* graphic_headers, + const std::vector<const GRSurface*>& graphic_items, size_t initial_selection, const DrawInterface& draw_funcs) - : Menu(initial_selection, draw_funcs), - graphic_headers_(graphic_headers), - graphic_items_(graphic_items) {} + : Menu(initial_selection, draw_funcs) { + graphic_headers_ = graphic_headers->Clone(); + graphic_items_.reserve(graphic_items.size()); + for (const auto& item : graphic_items) { + graphic_items_.emplace_back(item->Clone()); + } +} int GraphicMenu::Select(int sel) { CHECK_LE(graphic_items_.size(), static_cast<size_t>(std::numeric_limits<int>::max())); @@ -221,7 +226,7 @@ int GraphicMenu::Select(int sel) { int GraphicMenu::DrawHeader(int x, int y) const { draw_funcs_.SetColor(UIElement::HEADER); - draw_funcs_.DrawTextIcon(x, y, graphic_headers_); + draw_funcs_.DrawTextIcon(x, y, graphic_headers_.get()); return graphic_headers_->height; } @@ -242,7 +247,7 @@ int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) cons // Bold white text for the selected item. draw_funcs_.SetColor(UIElement::MENU_SEL_FG); } - draw_funcs_.DrawTextIcon(x, y + offset, item); + draw_funcs_.DrawTextIcon(x, y + offset, item.get()); offset += item->height; draw_funcs_.SetColor(UIElement::MENU); @@ -251,8 +256,8 @@ int GraphicMenu::DrawItems(int x, int y, int screen_width, bool long_press) cons return offset; } -bool GraphicMenu::Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers, - const std::vector<GRSurface*>& graphic_items) { +bool GraphicMenu::Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector<const GRSurface*>& graphic_items) { int offset = 0; if (!ValidateGraphicSurface(max_width, max_height, offset, graphic_headers)) { return false; @@ -307,7 +312,9 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) animation_fps_( android::base::GetIntProperty("ro.recovery.ui.animation_fps", kDefaultAnimationFps)), density_(static_cast<float>(android::base::GetIntProperty("ro.sf.lcd_density", 160)) / 160.f), - currentIcon(NONE), + current_icon_(NONE), + current_frame_(0), + intro_done_(false), progressBarType(EMPTY), progressScopeStart(0), progressScopeSize(0), @@ -322,10 +329,6 @@ ScreenRecoveryUI::ScreenRecoveryUI(bool scrollable_menu) show_text_ever(false), scrollable_menu_(scrollable_menu), file_viewer_text_(nullptr), - intro_frames(0), - loop_frames(0), - current_frame(0), - intro_done(false), stage(-1), max_stage(-1), locale_(""), @@ -340,23 +343,23 @@ ScreenRecoveryUI::~ScreenRecoveryUI() { gr_exit(); } -GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { - if (currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) { - return intro_done ? loopFrames[current_frame] : introFrames[current_frame]; +const GRSurface* ScreenRecoveryUI::GetCurrentFrame() const { + if (current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) { + return intro_done_ ? loop_frames_[current_frame_].get() : intro_frames_[current_frame_].get(); } - return error_icon; + return error_icon_.get(); } -GRSurface* ScreenRecoveryUI::GetCurrentText() const { - switch (currentIcon) { +const GRSurface* ScreenRecoveryUI::GetCurrentText() const { + switch (current_icon_) { case ERASING: - return erasing_text; + return erasing_text_.get(); case ERROR: - return error_text; + return error_text_.get(); case INSTALLING_UPDATE: - return installing_text; + return installing_text_.get(); case NO_COMMAND: - return no_command_text; + return no_command_text_.get(); case NONE: abort(); } @@ -391,20 +394,21 @@ static constexpr int kLayouts[LAYOUT_MAX][DIMENSION_MAX] = { }; int ScreenRecoveryUI::GetAnimationBaseline() const { - return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - gr_get_height(loopFrames[0]); + return GetTextBaseline() - PixelsFromDp(kLayouts[layout_][ICON]) - + gr_get_height(loop_frames_[0].get()); } int ScreenRecoveryUI::GetTextBaseline() const { return GetProgressBaseline() - PixelsFromDp(kLayouts[layout_][TEXT]) - - gr_get_height(installing_text); + gr_get_height(installing_text_.get()); } int ScreenRecoveryUI::GetProgressBaseline() const { - int elements_sum = gr_get_height(loopFrames[0]) + PixelsFromDp(kLayouts[layout_][ICON]) + - gr_get_height(installing_text) + PixelsFromDp(kLayouts[layout_][TEXT]) + - gr_get_height(progressBarFill); + int elements_sum = gr_get_height(loop_frames_[0].get()) + PixelsFromDp(kLayouts[layout_][ICON]) + + gr_get_height(installing_text_.get()) + PixelsFromDp(kLayouts[layout_][TEXT]) + + gr_get_height(progress_bar_fill_.get()); int bottom_gap = (ScreenHeight() - elements_sum) / 2; - return ScreenHeight() - bottom_gap - gr_get_height(progressBarFill); + return ScreenHeight() - bottom_gap - gr_get_height(progress_bar_fill_.get()); } // Clear the screen and draw the currently selected background icon (if any). @@ -413,20 +417,20 @@ void ScreenRecoveryUI::draw_background_locked() { pagesIdentical = false; gr_color(0, 0, 0, 255); gr_clear(); - if (currentIcon != NONE) { + if (current_icon_ != NONE) { if (max_stage != -1) { - int stage_height = gr_get_height(stageMarkerEmpty); - int stage_width = gr_get_width(stageMarkerEmpty); - int x = (ScreenWidth() - max_stage * gr_get_width(stageMarkerEmpty)) / 2; + int stage_height = gr_get_height(stage_marker_empty_.get()); + int stage_width = gr_get_width(stage_marker_empty_.get()); + int x = (ScreenWidth() - max_stage * gr_get_width(stage_marker_empty_.get())) / 2; int y = ScreenHeight() - stage_height - margin_height_; for (int i = 0; i < max_stage; ++i) { - GRSurface* stage_surface = (i < stage) ? stageMarkerFill : stageMarkerEmpty; - DrawSurface(stage_surface, 0, 0, stage_width, stage_height, x, y); + const auto& stage_surface = (i < stage) ? stage_marker_fill_ : stage_marker_empty_; + DrawSurface(stage_surface.get(), 0, 0, stage_width, stage_height, x, y); x += stage_width; } } - GRSurface* text_surface = GetCurrentText(); + const auto& text_surface = GetCurrentText(); int text_x = (ScreenWidth() - gr_get_width(text_surface)) / 2; int text_y = GetTextBaseline(); gr_color(255, 255, 255, 255); @@ -437,8 +441,8 @@ void ScreenRecoveryUI::draw_background_locked() { // Draws the animation and progress bar (if any) on the screen. Does not flip pages. Should only be // called with updateMutex locked. void ScreenRecoveryUI::draw_foreground_locked() { - if (currentIcon != NONE) { - GRSurface* frame = GetCurrentFrame(); + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); int frame_width = gr_get_width(frame); int frame_height = gr_get_height(frame); int frame_x = (ScreenWidth() - frame_width) / 2; @@ -447,8 +451,8 @@ void ScreenRecoveryUI::draw_foreground_locked() { } if (progressBarType != EMPTY) { - int width = gr_get_width(progressBarEmpty); - int height = gr_get_height(progressBarEmpty); + int width = gr_get_width(progress_bar_empty_.get()); + int height = gr_get_height(progress_bar_empty_.get()); int progress_x = (ScreenWidth() - width) / 2; int progress_y = GetProgressBaseline(); @@ -464,19 +468,20 @@ void ScreenRecoveryUI::draw_foreground_locked() { if (rtl_locale_) { // Fill the progress bar from right to left. if (pos > 0) { - DrawSurface(progressBarFill, width - pos, 0, pos, height, progress_x + width - pos, - progress_y); + DrawSurface(progress_bar_fill_.get(), width - pos, 0, pos, height, + progress_x + width - pos, progress_y); } if (pos < width - 1) { - DrawSurface(progressBarEmpty, 0, 0, width - pos, height, progress_x, progress_y); + DrawSurface(progress_bar_empty_.get(), 0, 0, width - pos, height, progress_x, progress_y); } } else { // Fill the progress bar from left to right. if (pos > 0) { - DrawSurface(progressBarFill, 0, 0, pos, height, progress_x, progress_y); + DrawSurface(progress_bar_fill_.get(), 0, 0, pos, height, progress_x, progress_y); } if (pos < width - 1) { - DrawSurface(progressBarEmpty, pos, 0, width - pos, height, progress_x + pos, progress_y); + DrawSurface(progress_bar_empty_.get(), pos, 0, width - pos, height, progress_x + pos, + progress_y); } } } @@ -518,15 +523,14 @@ void ScreenRecoveryUI::SelectAndShowBackgroundText(const std::vector<std::string SetLocale(locales_entries[sel]); std::vector<std::string> text_name = { "erasing_text", "error_text", "installing_text", "installing_security_text", "no_command_text" }; - std::unordered_map<std::string, std::unique_ptr<GRSurface, decltype(&free)>> surfaces; + std::unordered_map<std::string, std::unique_ptr<GRSurface>> surfaces; for (const auto& name : text_name) { - GRSurface* text_image = nullptr; - LoadLocalizedBitmap(name.c_str(), &text_image); + auto text_image = LoadLocalizedBitmap(name); if (!text_image) { Print("Failed to load %s\n", name.c_str()); return; } - surfaces.emplace(name, std::unique_ptr<GRSurface, decltype(&free)>(text_image, &free)); + surfaces.emplace(name, std::move(text_image)); } std::lock_guard<std::mutex> lg(updateMutex); @@ -753,16 +757,16 @@ void ScreenRecoveryUI::ProgressThreadLoop() { // update the installation animation, if active // skip this if we have a text overlay (too expensive to update) - if ((currentIcon == INSTALLING_UPDATE || currentIcon == ERASING) && !show_text) { - if (!intro_done) { - if (current_frame == intro_frames - 1) { - intro_done = true; - current_frame = 0; + if ((current_icon_ == INSTALLING_UPDATE || current_icon_ == ERASING) && !show_text) { + if (!intro_done_) { + if (current_frame_ == intro_frames_.size() - 1) { + intro_done_ = true; + current_frame_ = 0; } else { - ++current_frame; + ++current_frame_; } } else { - current_frame = (current_frame + 1) % loop_frames; + current_frame_ = (current_frame_ + 1) % loop_frames_.size(); } redraw = true; @@ -791,18 +795,23 @@ void ScreenRecoveryUI::ProgressThreadLoop() { } } -void ScreenRecoveryUI::LoadBitmap(const char* filename, GRSurface** surface) { - int result = res_create_display_surface(filename, surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; +std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadBitmap(const std::string& filename) { + GRSurface* surface; + if (auto result = res_create_display_surface(filename.c_str(), &surface); result < 0) { + LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; + return nullptr; } + return std::unique_ptr<GRSurface>(surface); } -void ScreenRecoveryUI::LoadLocalizedBitmap(const char* filename, GRSurface** surface) { - int result = res_create_localized_alpha_surface(filename, locale_.c_str(), surface); - if (result < 0) { - LOG(ERROR) << "couldn't load bitmap " << filename << " (error " << result << ")"; +std::unique_ptr<GRSurface> ScreenRecoveryUI::LoadLocalizedBitmap(const std::string& filename) { + GRSurface* surface; + if (auto result = res_create_localized_alpha_surface(filename.c_str(), locale_.c_str(), &surface); + result < 0) { + LOG(ERROR) << "Failed to load bitmap " << filename << " (error " << result << ")"; + return nullptr; } + return std::unique_ptr<GRSurface>(surface); } static char** Alloc2d(size_t rows, size_t cols) { @@ -817,9 +826,9 @@ static char** Alloc2d(size_t rows, size_t cols) { // Choose the right background string to display during update. void ScreenRecoveryUI::SetSystemUpdateText(bool security_update) { if (security_update) { - LoadLocalizedBitmap("installing_security_text", &installing_text); + installing_text_ = LoadLocalizedBitmap("installing_security_text"); } else { - LoadLocalizedBitmap("installing_text", &installing_text); + installing_text_ = LoadLocalizedBitmap("installing_text"); } Redraw(); } @@ -838,10 +847,6 @@ bool ScreenRecoveryUI::InitTextParams() { // TODO(xunchang) load localized text icons for the menu. (Init for screenRecoveryUI but // not wearRecoveryUI). bool ScreenRecoveryUI::LoadWipeDataMenuText() { - wipe_data_menu_header_text_ = nullptr; - factory_data_reset_text_ = nullptr; - try_again_text_ = nullptr; - return true; } @@ -869,21 +874,20 @@ bool ScreenRecoveryUI::Init(const std::string& locale) { // Set up the locale info. SetLocale(locale); - LoadBitmap("icon_error", &error_icon); + error_icon_ = LoadBitmap("icon_error"); - LoadBitmap("progress_empty", &progressBarEmpty); - LoadBitmap("progress_fill", &progressBarFill); + progress_bar_empty_ = LoadBitmap("progress_empty"); + progress_bar_fill_ = LoadBitmap("progress_fill"); + stage_marker_empty_ = LoadBitmap("stage_empty"); + stage_marker_fill_ = LoadBitmap("stage_fill"); - LoadBitmap("stage_empty", &stageMarkerEmpty); - LoadBitmap("stage_fill", &stageMarkerFill); + erasing_text_ = LoadLocalizedBitmap("erasing_text"); + no_command_text_ = LoadLocalizedBitmap("no_command_text"); + error_text_ = LoadLocalizedBitmap("error_text"); - // Background text for "installing_update" could be "installing update" - // or "installing security update". It will be set after UI init according - // to commands in BCB. - installing_text = nullptr; - LoadLocalizedBitmap("erasing_text", &erasing_text); - LoadLocalizedBitmap("no_command_text", &no_command_text); - LoadLocalizedBitmap("error_text", &error_text); + // Background text for "installing_update" could be "installing update" or + // "installing security update". It will be set after Init() according to the commands in BCB. + installing_text_.reset(); LoadWipeDataMenuText(); @@ -915,32 +919,34 @@ void ScreenRecoveryUI::LoadAnimation() { } } - intro_frames = intro_frame_names.size(); - loop_frames = loop_frame_names.size(); + size_t intro_frames = intro_frame_names.size(); + size_t loop_frames = loop_frame_names.size(); // It's okay to not have an intro. - if (intro_frames == 0) intro_done = true; + if (intro_frames == 0) intro_done_ = true; // But you must have an animation. if (loop_frames == 0) abort(); std::sort(intro_frame_names.begin(), intro_frame_names.end()); std::sort(loop_frame_names.begin(), loop_frame_names.end()); - introFrames = new GRSurface*[intro_frames]; - for (size_t i = 0; i < intro_frames; i++) { - LoadBitmap(intro_frame_names.at(i).c_str(), &introFrames[i]); + intro_frames_.clear(); + intro_frames_.reserve(intro_frames); + for (const auto& frame_name : intro_frame_names) { + intro_frames_.emplace_back(LoadBitmap(frame_name)); } - loopFrames = new GRSurface*[loop_frames]; - for (size_t i = 0; i < loop_frames; i++) { - LoadBitmap(loop_frame_names.at(i).c_str(), &loopFrames[i]); + loop_frames_.clear(); + loop_frames_.reserve(loop_frames); + for (const auto& frame_name : loop_frame_names) { + loop_frames_.emplace_back(LoadBitmap(frame_name)); } } void ScreenRecoveryUI::SetBackground(Icon icon) { std::lock_guard<std::mutex> lg(updateMutex); - currentIcon = icon; + current_icon_ = icon; update_screen_locked(); } @@ -972,7 +978,7 @@ void ScreenRecoveryUI::SetProgress(float fraction) { if (fraction > 1.0) fraction = 1.0; if (progressBarType == DETERMINATE && fraction > progress) { // Skip updates that aren't visibly different. - int width = gr_get_width(progressBarEmpty); + int width = gr_get_width(progress_bar_empty_.get()); float scale = width * progressScopeSize; if ((int)(progress * scale) != (int)(fraction * scale)) { progress = fraction; @@ -1115,11 +1121,10 @@ void ScreenRecoveryUI::ShowFile(const std::string& filename) { text_row_ = old_text_row; } -std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu(GRSurface* graphic_header, - const std::vector<GRSurface*>& graphic_items, - const std::vector<std::string>& text_headers, - const std::vector<std::string>& text_items, - size_t initial_selection) const { +std::unique_ptr<Menu> ScreenRecoveryUI::CreateMenu( + const GRSurface* graphic_header, const std::vector<const GRSurface*>& graphic_items, + const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items, + size_t initial_selection) const { // horizontal unusable area: margin width + menu indent size_t max_width = ScreenWidth() - margin_width_ - kMenuIndent; // vertical unusable area: margin height + title lines + helper message + high light bar. @@ -1235,9 +1240,9 @@ size_t ScreenRecoveryUI::ShowMenu(const std::vector<std::string>& headers, size_t ScreenRecoveryUI::ShowPromptWipeDataMenu(const std::vector<std::string>& backup_headers, const std::vector<std::string>& backup_items, const std::function<int(int, bool)>& key_handler) { - auto wipe_data_menu = - CreateMenu(wipe_data_menu_header_text_, { try_again_text_, factory_data_reset_text_ }, - backup_headers, backup_items, 0); + auto wipe_data_menu = CreateMenu(wipe_data_menu_header_text_.get(), + { try_again_text_.get(), factory_data_reset_text_.get() }, + backup_headers, backup_items, 0); if (wipe_data_menu == nullptr) { return 0; } diff --git a/screen_ui.h b/screen_ui.h index 2d0d97d8d..ff245a2fb 100644 --- a/screen_ui.h +++ b/screen_ui.h @@ -162,32 +162,31 @@ class TextMenu : public Menu { int char_height_; }; -// This class uses GRSurfaces* as the menu header and items. +// This class uses GRSurface's as the menu header and items. class GraphicMenu : public Menu { public: // Constructs a Menu instance with the given |headers|, |items| and properties. Sets the initial - // selection to |initial_selection|. - GraphicMenu(GRSurface* graphic_headers, const std::vector<GRSurface*>& graphic_items, + // selection to |initial_selection|. |headers| and |items| will be made local copies. + GraphicMenu(const GRSurface* graphic_headers, const std::vector<const GRSurface*>& graphic_items, size_t initial_selection, const DrawInterface& draw_funcs); int Select(int sel) override; int DrawHeader(int x, int y) const override; int DrawItems(int x, int y, int screen_width, bool long_press) const override; - // Checks if all the header and items are valid GRSurfaces; and that they can fit in the area + // Checks if all the header and items are valid GRSurface's; and that they can fit in the area // defined by |max_width| and |max_height|. - static bool Validate(size_t max_width, size_t max_height, GRSurface* graphic_headers, - const std::vector<GRSurface*>& graphic_items); + static bool Validate(size_t max_width, size_t max_height, const GRSurface* graphic_headers, + const std::vector<const GRSurface*>& graphic_items); // Returns true if |surface| fits on the screen with a vertical offset |y|. static bool ValidateGraphicSurface(size_t max_width, size_t max_height, int y, const GRSurface* surface); private: - // Pointers to the menu headers and items in graphic icons. This class does not have the ownership - // of the these objects. - GRSurface* graphic_headers_; - std::vector<GRSurface*> graphic_items_; + // Menu headers and items in graphic icons. These are the copies owned by the class instance. + std::unique_ptr<GRSurface> graphic_headers_; + std::vector<std::unique_ptr<GRSurface>> graphic_items_; }; // Implementation of RecoveryUI appropriate for devices with a screen @@ -243,6 +242,7 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { protected: static constexpr int kMenuIndent = 4; + // The margin that we don't want to use for showing texts (e.g. round screen, or screen with // rounded corners). const int margin_width_; @@ -261,8 +261,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { // Creates a GraphicMenu with |graphic_header| and |graphic_items|. If the GraphicMenu isn't // valid or it doesn't fit on the screen; falls back to create a TextMenu instead. If succeeds, // returns a unique pointer to the created menu; otherwise returns nullptr. - virtual std::unique_ptr<Menu> CreateMenu(GRSurface* graphic_header, - const std::vector<GRSurface*>& graphic_items, + virtual std::unique_ptr<Menu> CreateMenu(const GRSurface* graphic_header, + const std::vector<const GRSurface*>& graphic_items, const std::vector<std::string>& text_headers, const std::vector<std::string>& text_items, size_t initial_selection) const; @@ -288,8 +288,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { virtual void update_screen_locked(); virtual void update_progress_locked(); - GRSurface* GetCurrentFrame() const; - GRSurface* GetCurrentText() const; + const GRSurface* GetCurrentFrame() const; + const GRSurface* GetCurrentText() const; void ProgressThreadLoop(); @@ -299,8 +299,8 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { void ClearText(); void LoadAnimation(); - void LoadBitmap(const char* filename, GRSurface** surface); - void LoadLocalizedBitmap(const char* filename, GRSurface** surface); + std::unique_ptr<GRSurface> LoadBitmap(const std::string& filename); + std::unique_ptr<GRSurface> LoadLocalizedBitmap(const std::string& filename); int PixelsFromDp(int dp) const; virtual int GetAnimationBaseline() const; @@ -324,30 +324,34 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { int DrawTextLines(int x, int y, const std::vector<std::string>& lines) const override; int DrawWrappedTextLines(int x, int y, const std::vector<std::string>& lines) const override; - Icon currentIcon; - // The layout to use. int layout_; - GRSurface* error_icon; - - GRSurface* erasing_text; - GRSurface* error_text; - GRSurface* installing_text; - GRSurface* no_command_text; - - // Graphs for the wipe data menu - GRSurface* wipe_data_menu_header_text_; - GRSurface* try_again_text_; - GRSurface* factory_data_reset_text_; - - GRSurface** introFrames; - GRSurface** loopFrames; - - GRSurface* progressBarEmpty; - GRSurface* progressBarFill; - GRSurface* stageMarkerEmpty; - GRSurface* stageMarkerFill; + // The images that contain localized texts. + std::unique_ptr<GRSurface> erasing_text_; + std::unique_ptr<GRSurface> error_text_; + std::unique_ptr<GRSurface> installing_text_; + std::unique_ptr<GRSurface> no_command_text_; + + // Localized text images for the wipe data menu. + std::unique_ptr<GRSurface> wipe_data_menu_header_text_; + std::unique_ptr<GRSurface> try_again_text_; + std::unique_ptr<GRSurface> factory_data_reset_text_; + + // current_icon_ points to one of the frames in intro_frames_ or loop_frames_, indexed by + // current_frame_, or error_icon_. + Icon current_icon_; + std::unique_ptr<GRSurface> error_icon_; + std::vector<std::unique_ptr<GRSurface>> intro_frames_; + std::vector<std::unique_ptr<GRSurface>> loop_frames_; + size_t current_frame_; + bool intro_done_; + + // progress_bar and stage_marker images. + std::unique_ptr<GRSurface> progress_bar_empty_; + std::unique_ptr<GRSurface> progress_bar_fill_; + std::unique_ptr<GRSurface> stage_marker_empty_; + std::unique_ptr<GRSurface> stage_marker_fill_; ProgressType progressBarType; @@ -377,13 +381,6 @@ class ScreenRecoveryUI : public RecoveryUI, public DrawInterface { std::thread progress_thread_; std::atomic<bool> progress_thread_stopped_{ false }; - // Number of intro frames and loop frames in the animation. - size_t intro_frames; - size_t loop_frames; - - size_t current_frame; - bool intro_done; - int stage, max_stage; int char_width_; diff --git a/tests/Android.bp b/tests/Android.bp index 2cfc32572..5165ccb33 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -92,7 +92,7 @@ librecovery_static_libs = [ "libhidl-gen-utils", "libhidlbase", "libhidltransport", - "libhwbinder", + "libhwbinder_noltopgo", "libbinderthreadstate", "libvndksupport", "libtinyxml2", diff --git a/tests/component/applypatch_modes_test.cpp b/tests/component/applypatch_modes_test.cpp index ce01f4fd5..08414b796 100644 --- a/tests/component/applypatch_modes_test.cpp +++ b/tests/component/applypatch_modes_test.cpp @@ -23,7 +23,6 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/strings.h> -#include <android-base/test_utils.h> #include <bsdiff/bsdiff.h> #include <gtest/gtest.h> #include <openssl/sha.h> diff --git a/tests/component/bootloader_message_test.cpp b/tests/component/bootloader_message_test.cpp index 6cc59a495..b005d199c 100644 --- a/tests/component/bootloader_message_test.cpp +++ b/tests/component/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/component/imgdiff_test.cpp b/tests/component/imgdiff_test.cpp index cb4868a4a..e76ccbdfb 100644 --- a/tests/component/imgdiff_test.cpp +++ b/tests/component/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> diff --git a/tests/component/install_test.cpp b/tests/component/install_test.cpp index 08b429000..27a01cb32 100644 --- a/tests/component/install_test.cpp +++ b/tests/component/install_test.cpp @@ -20,13 +20,13 @@ #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 <android-base/test_utils.h> #include <gtest/gtest.h> #include <vintf/VintfObjectRecovery.h> #include <ziparchive/zip_archive.h> @@ -36,15 +36,23 @@ #include "otautil/paths.h" #include "private/install.h" -TEST(InstallTest, verify_package_compatibility_no_entry) { - TemporaryFile temp_file; - FILE* zip_file = fdopen(temp_file.release(), "w"); +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); - // The archive must have something to be opened correctly. - ASSERT_EQ(0, writer.StartEntry("dummy_entry", 0)); - ASSERT_EQ(0, writer.FinishEntry()); + 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; @@ -55,12 +63,7 @@ TEST(InstallTest, verify_package_compatibility_no_entry) { 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)); + BuildZipArchive({ { "compatibility.zip", "" } }, temp_file.release(), kCompressStored); // Empty compatibility zip entry. ZipArchiveHandle zip; @@ -71,77 +74,51 @@ TEST(InstallTest, verify_package_compatibility_invalid_entry) { 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)); + 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::string metadata; - ASSERT_TRUE(read_metadata_from_package(zip, &metadata)); - ASSERT_EQ(content, metadata); + std::map<std::string, std::string> metadata; + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + ASSERT_EQ("defg", metadata["abc"]); 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)); + BuildZipArchive({ { "META-INF/com/android/metadata", content } }, temp_file2.release(), + kCompressDeflated); ASSERT_EQ(0, OpenArchive(temp_file2.path, &zip)); metadata.clear(); - ASSERT_TRUE(read_metadata_from_package(zip, &metadata)); - ASSERT_EQ(content, metadata); + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); + ASSERT_EQ("defg", metadata["abc"]); 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)); + BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored); ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); - std::string metadata; - ASSERT_FALSE(read_metadata_from_package(zip, &metadata)); + std::map<std::string, std::string> metadata; + ASSERT_FALSE(ReadMetadataFromPackage(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)); + BuildZipArchive({ { "system_manifest.xml", malformed_xml } }, compatibility_zip_file.release(), + kCompressDeflated); 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)); + BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(), + kCompressStored); ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); @@ -166,27 +143,15 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml 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)); + BuildZipArchive({ { "system_manifest.xml", system_manifest_xml_content } }, + compatibility_zip_file.release(), kCompressDeflated); 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)); + BuildZipArchive({ { "compatibility.zip", compatibility_zip_content } }, temp_file.release(), + kCompressStored); ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); @@ -202,13 +167,8 @@ TEST(InstallTest, verify_package_compatibility_with_libvintf_system_manifest_xml TEST(InstallTest, SetUpNonAbUpdateCommands) { 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)); + BuildZipArchive({ { UPDATE_BINARY_NAME, "" } }, temp_file.release(), kCompressStored); ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); @@ -246,13 +206,8 @@ TEST(InstallTest, SetUpNonAbUpdateCommands) { TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) { 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)); + BuildZipArchive({ { "dummy_entry", "" } }, temp_file.release(), kCompressStored); // Missing update binary. ZipArchiveHandle zip; @@ -268,16 +223,8 @@ TEST(InstallTest, SetUpNonAbUpdateCommands_MissingUpdateBinary) { static void VerifyAbUpdateCommands(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", ""); @@ -288,21 +235,27 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t 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)); + 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)); 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::vector<std::string> cmd; + + std::map<std::string, std::string> metadata; + ASSERT_TRUE(ReadMetadataFromPackage(zip, &metadata)); if (success) { + ASSERT_EQ(0, CheckPackageMetadata(metadata, OtaType::AB)); + + int status_fd = 10; + std::string package = "/path/to/update.zip"; + std::vector<std::string> cmd; ASSERT_EQ(0, SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); ASSERT_EQ(5U, cmd.size()); ASSERT_EQ("/system/bin/update_engine_sideload", cmd[0]); @@ -311,7 +264,7 @@ static void VerifyAbUpdateCommands(const std::string& serialno, bool success = t ASSERT_EQ("--headers=" + properties, cmd[3]); ASSERT_EQ("--status_fd=" + std::to_string(status_fd), cmd[4]); } else { - ASSERT_EQ(INSTALL_ERROR, SetUpAbUpdateCommands(package, zip, status_fd, &cmd)); + ASSERT_EQ(INSTALL_ERROR, CheckPackageMetadata(metadata, OtaType::AB)); } CloseArchive(zip); } @@ -323,13 +276,7 @@ TEST(InstallTest, SetUpAbUpdateCommands) { TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) { 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", ""); @@ -339,10 +286,13 @@ TEST(InstallTest, SetUpAbUpdateCommands_MissingPayloadPropertiesTxt) { "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)); + + BuildZipArchive( + { + { "payload.bin", "" }, + { "META-INF/com/android/metadata", metadata }, + }, + temp_file.release(), kCompressStored); ZipArchiveHandle zip; ASSERT_EQ(0, OpenArchive(temp_file.path, &zip)); @@ -381,3 +331,241 @@ TEST(InstallTest, SetUpAbUpdateCommands_MultipleSerialnos) { // String with the matching serialno should pass the verification. VerifyAbUpdateCommands(long_serialno); } + +static void test_check_package_metadata(const std::string& metadata_string, OtaType ota_type, + int 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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + + // 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"); + test_check_package_metadata(metadata, OtaType::AB, 0); + + test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); +} + +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"); + test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + + // device type mismatches + metadata = android::base::Join( + std::vector<std::string>{ + "ota-type=BRICK", + "pre-device=dummy_device_type", + }, + "\n"); + test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); +} + +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"); + test_check_package_metadata(metadata, OtaType::BRICK, 0); + + // Serial number mismatches + metadata = android::base::Join( + std::vector<std::string>{ + "ota-type=BRICK", + "pre-device=" + device, + "serialno=dummy_serial", + }, + "\n"); + test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + + 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"); + test_check_package_metadata(metadata, OtaType::BRICK, 0); +} + +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 (size_t c = 'a'; c <= 'z'; c++) { + serial_numbers.emplace_back(c, serialno.size()); + } + + // 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"); + test_check_package_metadata(metadata, OtaType::BRICK, INSTALL_ERROR); + + 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"); + test_check_package_metadata(metadata, OtaType::BRICK, 0); +} + +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"); + test_check_package_metadata(metadata, OtaType::AB, 0); + + 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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); +} + +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"); + test_check_package_metadata(metadata, OtaType::AB, 0); + + 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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); +} + +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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + + // 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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + + // 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"); + test_check_package_metadata(metadata, OtaType::AB, INSTALL_ERROR); + + 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"); + test_check_package_metadata(metadata, OtaType::AB, 0); +} diff --git a/tests/component/resources_test.cpp b/tests/component/resources_test.cpp index 54329db22..d7fdb8fa0 100644 --- a/tests/component/resources_test.cpp +++ b/tests/component/resources_test.cpp @@ -101,7 +101,7 @@ TEST_P(ResourcesTest, ValidateLocale) { EXPECT_LT(0, len) << "Locale string should be non-empty."; EXPECT_NE(0, row[5]) << "Locale string is missing."; - ASSERT_GT(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file."; + 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)); diff --git a/tests/component/sideload_test.cpp b/tests/component/sideload_test.cpp index b7109fcc2..d5e074c63 100644 --- a/tests/component/sideload_test.cpp +++ b/tests/component/sideload_test.cpp @@ -21,7 +21,6 @@ #include <android-base/file.h> #include <android-base/strings.h> -#include <android-base/test_utils.h> #include <gtest/gtest.h> #include "fuse_sideload.h" diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp index 55baca2e3..e97d589a6 100644 --- a/tests/component/uncrypt_test.cpp +++ b/tests/component/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/component/update_verifier_test.cpp b/tests/component/update_verifier_test.cpp index 2420c27fe..0a594037c 100644 --- a/tests/component/update_verifier_test.cpp +++ b/tests/component/update_verifier_test.cpp @@ -24,7 +24,6 @@ #include <android-base/file.h> #include <android-base/properties.h> #include <android-base/strings.h> -#include <android-base/test_utils.h> #include <google/protobuf/repeated_field.h> #include <gtest/gtest.h> diff --git a/tests/component/updater_test.cpp b/tests/component/updater_test.cpp index 24c63e776..a0a7b66ab 100644 --- a/tests/component/updater_test.cpp +++ b/tests/component/updater_test.cpp @@ -23,6 +23,7 @@ #include <algorithm> #include <memory> #include <string> +#include <string_view> #include <unordered_map> #include <vector> @@ -32,7 +33,6 @@ #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> @@ -134,9 +134,9 @@ static void RunBlockImageUpdate(bool is_verify, const PackageEntries& entries, CloseArchive(handle); } -static std::string get_sha1(const std::string& content) { +static std::string GetSha1(std::string_view content) { uint8_t digest[SHA_DIGEST_LENGTH]; - SHA1(reinterpret_cast<const uint8_t*>(content.c_str()), content.size(), digest); + SHA1(reinterpret_cast<const uint8_t*>(content.data()), content.size(), digest); return print_sha1(digest); } @@ -187,7 +187,7 @@ class UpdaterTest : public ::testing::Test { // Clear partition updated marker if any. std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED"; + updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); } @@ -223,14 +223,14 @@ TEST_F(UpdaterTest, patch_partition_check) { std::string source_content; ASSERT_TRUE(android::base::ReadFileToString(source_file, &source_content)); size_t source_size = source_content.size(); - std::string source_hash = get_sha1(source_content); + 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 = get_sha1(target_content); + std::string target_hash = GetSha1(target_content); Partition target(target_file, target_size, target_hash); // One argument is not valid. @@ -619,54 +619,100 @@ TEST_F(UpdaterTest, block_image_update_parsing_error) { RunBlockImageUpdate(false, entries, image_file_, "", kArgsParsingFailure); } -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'); - +// 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*>(src_content.data()), src_content.size(), - reinterpret_cast<const uint8_t*>(tgt_content.data()), tgt_content.size(), - patch_file.path, nullptr)); + 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 = get_sha1(src_content); - std::string tgt_hash = get_sha1(tgt_content); + 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", - "2", + std::to_string(target_blocks), "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, + "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 }; - PackageEntries entries{ + *entries = { { "new_data", "" }, { "patch_data", patch_content }, { "transfer_list", android::base::Join(transfer_list, '\n') }, }; +} - ASSERT_TRUE(android::base::WriteStringToFile(src_content, image_file_)); - +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_content; - ASSERT_TRUE(android::base::ReadFileToString(image_file_, &updated_content)); - ASSERT_EQ(tgt_content, updated_content); + 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 = get_sha1(src_content); + 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 @@ -692,7 +738,7 @@ TEST_F(UpdaterTest, block_image_update_fail) { RunBlockImageUpdate(false, entries, image_file_, ""); // Updater generates the stash name based on the input file name. - std::string name_digest = get_sha1(image_file_); + 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. @@ -796,9 +842,9 @@ 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 = get_sha1(block1); - std::string block2_hash = get_sha1(block2); - std::string block3_hash = get_sha1(block3); + 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{ @@ -864,8 +910,8 @@ TEST_F(UpdaterTest, last_command_update) { TEST_F(UpdaterTest, last_command_update_unresumable) { std::string block1(4096, '1'); std::string block2(4096, '2'); - std::string block1_hash = get_sha1(block1); - std::string block2_hash = get_sha1(block2); + 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{ @@ -901,9 +947,9 @@ 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 = get_sha1(block1); - std::string block2_hash = get_sha1(block2); - std::string block3_hash = get_sha1(block3); + 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 @@ -972,7 +1018,7 @@ class ResumableUpdaterTest : public testing::TestWithParam<size_t> { // Clear partition updated marker if any. std::string updated_marker{ temp_stash_base_.path }; - updated_marker += "/" + get_sha1(image_temp_file_.path) + ".UPDATED"; + updated_marker += "/" + GetSha1(image_temp_file_.path) + ".UPDATED"; ASSERT_TRUE(android::base::RemoveFileIfExists(updated_marker)); } @@ -1003,10 +1049,10 @@ static std::vector<std::string> GenerateTransferList() { std::string i(4096, 'i'); std::string zero(4096, '\0'); - std::string a_hash = get_sha1(a); - std::string b_hash = get_sha1(b); - std::string c_hash = get_sha1(c); - std::string e_hash = get_sha1(e); + 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, "-"); @@ -1027,8 +1073,8 @@ static std::vector<std::string> GenerateTransferList() { // patch 1: "b d c" -> "g" TemporaryFile patch_file_bdc_g; std::string bdc = b + d + c; - std::string bdc_hash = get_sha1(bdc); - std::string g_hash = get_sha1(g); + 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)); @@ -1038,9 +1084,9 @@ static std::vector<std::string> GenerateTransferList() { // 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 = get_sha1(abcd); + std::string abcd_hash = GetSha1(abcd); std::string dcb = d + c + b; - std::string dcb_hash = get_sha1(dcb); + 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)); diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index d110c37e0..9fcaa0b73 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -27,9 +27,11 @@ #include <android-base/file.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include <android-base/test_utils.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" @@ -148,6 +150,35 @@ TEST(VerifierTest, LoadCertificateFromBuffer_sha256_ec256bits) { VerifyPackageWithSingleCertificate("otasigned_v5.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()); @@ -206,8 +237,9 @@ class VerifierTest : public testing::TestWithParam<std::vector<std::string>> { } 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)); + 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()); } } @@ -221,70 +253,10 @@ 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)); + 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)); @@ -298,12 +270,9 @@ TEST(VerifierTest, BadPackage_AlteredFooter) { } 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)); + 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)); @@ -324,13 +293,9 @@ TEST(VerifierTest, BadPackage_AlteredContent) { } 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)); + 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; diff --git a/tests/testdata/jarsigned.zip b/tests/testdata/jarsigned.zip Binary files differdeleted file mode 100644 index 8b1ef8bdd..000000000 --- a/tests/testdata/jarsigned.zip +++ /dev/null diff --git a/tests/testdata/patch.bsdiff b/tests/testdata/patch.bsdiff Binary files differdeleted file mode 100644 index b78d38573..000000000 --- a/tests/testdata/patch.bsdiff +++ /dev/null diff --git a/tests/testdata/unsigned.zip b/tests/testdata/unsigned.zip Binary files differdeleted file mode 100644 index 24e3eadac..000000000 --- a/tests/testdata/unsigned.zip +++ /dev/null diff --git a/tests/unit/applypatch_test.cpp b/tests/unit/applypatch_test.cpp index 066f981b4..794f2c103 100644 --- a/tests/unit/applypatch_test.cpp +++ b/tests/unit/applypatch_test.cpp @@ -32,7 +32,6 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/stringprintf.h> -#include <android-base/test_utils.h> #include <android-base/unique_fd.h> #include <gtest/gtest.h> diff --git a/tests/unit/dirutil_test.cpp b/tests/unit/dirutil_test.cpp index 1ca786c28..4dd111a70 100644 --- a/tests/unit/dirutil_test.cpp +++ b/tests/unit/dirutil_test.cpp @@ -20,7 +20,7 @@ #include <string> -#include <android-base/test_utils.h> +#include <android-base/file.h> #include <gtest/gtest.h> #include "otautil/dirutil.h" diff --git a/tests/unit/minui_test.cpp b/tests/unit/minui_test.cpp index cad6a3d79..d68e5e3a1 100644 --- a/tests/unit/minui_test.cpp +++ b/tests/unit/minui_test.cpp @@ -15,8 +15,9 @@ */ #include <stdint.h> +#include <stdlib.h> -#include <memory> +#include <vector> #include <gtest/gtest.h> @@ -25,8 +26,19 @@ TEST(GRSurfaceTest, Create_aligned) { static constexpr size_t kSurfaceDataAlignment = 8; for (size_t data_size = 100; data_size < 128; data_size++) { - std::unique_ptr<GRSurface> surface(GRSurface::Create(data_size)); + auto surface = GRSurface::Create(10, 1, 10, 1, data_size); ASSERT_TRUE(surface); ASSERT_EQ(0, reinterpret_cast<uintptr_t>(surface->data()) % kSurfaceDataAlignment); } } + +TEST(GRSurfaceTest, Clone) { + static constexpr size_t kImageSize = 10 * 50; + auto image = GRSurface::Create(50, 10, 50, 1, kImageSize); + for (auto i = 0; i < kImageSize; i++) { + image->data()[i] = rand() % 128; + } + auto image_copy = image->Clone(); + ASSERT_EQ(std::vector(image->data(), image->data() + kImageSize), + std::vector(image_copy->data(), image_copy->data() + kImageSize)); +} diff --git a/tests/unit/parse_install_logs_test.cpp b/tests/unit/parse_install_logs_test.cpp index 8061f3be1..72169a0c6 100644 --- a/tests/unit/parse_install_logs_test.cpp +++ b/tests/unit/parse_install_logs_test.cpp @@ -20,7 +20,6 @@ #include <android-base/file.h> #include <android-base/strings.h> -#include <android-base/test_utils.h> #include <gtest/gtest.h> #include "otautil/parse_install_logs.h" diff --git a/tests/unit/screen_ui_test.cpp b/tests/unit/screen_ui_test.cpp index 0014e45f1..09c49977f 100644 --- a/tests/unit/screen_ui_test.cpp +++ b/tests/unit/screen_ui_test.cpp @@ -23,10 +23,11 @@ #include <string> #include <vector> +#include <android-base/file.h> #include <android-base/logging.h> #include <android-base/stringprintf.h> -#include <android-base/test_utils.h> #include <gtest/gtest.h> +#include <gtest/gtest_prod.h> #include "common/test_constants.h" #include "device.h" @@ -69,17 +70,6 @@ class ScreenUITest : public testing::Test { MockDrawFunctions draw_funcs_; }; -// TODO(xunchang) Create a constructor. -static GRSurface CreateFakeGRSurface(int width, int height, int row_bytes, int pixel_bytes) { - GRSurface fake_surface; - fake_surface.width = width; - fake_surface.height = height; - fake_surface.row_bytes = row_bytes; - fake_surface.pixel_bytes = pixel_bytes; - - return fake_surface; -} - TEST_F(ScreenUITest, StartPhoneMenuSmoke) { TextMenu menu(false, 10, 20, HEADERS, ITEMS, 0, 20, draw_funcs_); ASSERT_FALSE(menu.scrollable()); @@ -241,9 +231,14 @@ TEST_F(ScreenUITest, WearMenuSelectItemsOverflow) { } TEST_F(ScreenUITest, GraphicMenuSelection) { - auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1); - std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface }; - GraphicMenu menu(&fake_surface, items, 0, draw_funcs_); + auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50); + 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()); @@ -263,18 +258,23 @@ TEST_F(ScreenUITest, GraphicMenuSelection) { } TEST_F(ScreenUITest, GraphicMenuValidate) { - auto fake_surface = CreateFakeGRSurface(50, 50, 50, 1); - std::vector<GRSurface*> items = { &fake_surface, &fake_surface, &fake_surface }; + auto image = GRSurface::Create(50, 50, 50, 1, 50 * 50); + auto header = image->Clone(); + std::vector<const GRSurface*> items = { + image.get(), + image.get(), + image.get(), + }; - ASSERT_TRUE(GraphicMenu::Validate(200, 200, &fake_surface, items)); + ASSERT_TRUE(GraphicMenu::Validate(200, 200, header.get(), items)); // Menu exceeds the horizontal boundary. - auto wide_surface = CreateFakeGRSurface(300, 50, 300, 1); - ASSERT_FALSE(GraphicMenu::Validate(299, 200, &wide_surface, items)); + auto wide_surface = GRSurface::Create(300, 50, 300, 1, 300 * 50); + ASSERT_FALSE(GraphicMenu::Validate(299, 200, wide_surface.get(), items)); // Menu exceeds the vertical boundary. - items.push_back(&fake_surface); - ASSERT_FALSE(GraphicMenu::Validate(200, 249, &fake_surface, items)); + items.emplace_back(image.get()); + ASSERT_FALSE(GraphicMenu::Validate(200, 249, header.get(), items)); } static constexpr int kMagicAction = 101; @@ -307,24 +307,13 @@ class TestableScreenRecoveryUI : public ScreenRecoveryUI { int KeyHandler(int key, bool visible) const; - // The following functions expose the protected members for test purpose. - void RunLoadAnimation() { - LoadAnimation(); - } - - size_t GetLoopFrames() const { - return loop_frames; - } - - size_t GetIntroFrames() const { - return intro_frames; - } - - bool GetRtlLocale() const { - return rtl_locale_; - } - private: + FRIEND_TEST(ScreenRecoveryUITest, Init); + FRIEND_TEST(ScreenRecoveryUITest, RtlLocale); + FRIEND_TEST(ScreenRecoveryUITest, RtlLocaleWithSuffix); + FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation); + FRIEND_TEST(ScreenRecoveryUITest, LoadAnimation_MissingAnimation); + std::vector<KeyCode> key_buffer_; size_t key_buffer_index_; }; @@ -388,7 +377,7 @@ TEST_F(ScreenRecoveryUITest, Init) { ASSERT_TRUE(ui_->Init(kTestLocale)); ASSERT_EQ(kTestLocale, ui_->GetLocale()); - ASSERT_FALSE(ui_->GetRtlLocale()); + ASSERT_FALSE(ui_->rtl_locale_); ASSERT_FALSE(ui_->IsTextVisible()); ASSERT_FALSE(ui_->WasTextEverVisible()); } @@ -416,14 +405,14 @@ TEST_F(ScreenRecoveryUITest, RtlLocale) { RETURN_IF_NO_GRAPHICS; ASSERT_TRUE(ui_->Init(kTestRtlLocale)); - ASSERT_TRUE(ui_->GetRtlLocale()); + ASSERT_TRUE(ui_->rtl_locale_); } TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) { RETURN_IF_NO_GRAPHICS; ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix)); - ASSERT_TRUE(ui_->GetRtlLocale()); + ASSERT_TRUE(ui_->rtl_locale_); } TEST_F(ScreenRecoveryUITest, ShowMenu) { @@ -548,10 +537,10 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation) { } Paths::Get().set_resource_dir(resource_dir.path); - ui_->RunLoadAnimation(); + ui_->LoadAnimation(); - ASSERT_EQ(2u, ui_->GetIntroFrames()); - ASSERT_EQ(3u, ui_->GetLoopFrames()); + 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())); @@ -567,7 +556,7 @@ TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) { Paths::Get().set_resource_dir("/proc/self"); ::testing::FLAGS_gtest_death_test_style = "threadsafe"; - ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), ""); + 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 de8ff7065..77625dbe9 100644 --- a/tests/unit/sysutil_test.cpp +++ b/tests/unit/sysutil_test.cpp @@ -17,7 +17,6 @@ #include <string> #include <android-base/file.h> -#include <android-base/test_utils.h> #include <gtest/gtest.h> #include "otautil/sysutil.h" diff --git a/tests/unit/zip_test.cpp b/tests/unit/zip_test.cpp index 47f33d9ea..dfe617ebe 100644 --- a/tests/unit/zip_test.cpp +++ b/tests/unit/zip_test.cpp @@ -21,7 +21,6 @@ #include <vector> #include <android-base/file.h> -#include <android-base/test_utils.h> #include <gtest/gtest.h> #include <ziparchive/zip_archive.h> diff --git a/tools/image_generator/Android.bp b/tools/image_generator/Android.bp index 3f718fec5..ce6e277bc 100644 --- a/tools/image_generator/Android.bp +++ b/tools/image_generator/Android.bp @@ -17,7 +17,11 @@ java_library_host { manifest: "ImageGenerator.mf", + static_libs: [ + "commons-cli-1.2", + ], + srcs: [ "ImageGenerator.java", ], -}
\ No newline at end of file +} diff --git a/tools/image_generator/ImageGenerator.java b/tools/image_generator/ImageGenerator.java index f2262166a..8730945b5 100644 --- a/tools/image_generator/ImageGenerator.java +++ b/tools/image_generator/ImageGenerator.java @@ -16,6 +16,16 @@ package com.android.recovery.tools; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + import java.awt.Color; import java.awt.Font; import java.awt.FontFormatException; @@ -26,369 +36,586 @@ import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.TreeMap; +import java.util.Set; import java.util.StringTokenizer; +import java.util.TreeMap; import javax.imageio.ImageIO; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * Command line tool to generate the localized image for recovery mode. - */ +/** Command line tool to generate the localized image for recovery mode. */ public class ImageGenerator { - // Initial height of the image to draw. - private static final int INITIAL_HEIGHT = 20000; - - private static final float DEFAULT_FONT_SIZE = 40; - - // This is the canvas we used to draw texts. - private BufferedImage mBufferedImage; - - // The width in pixels of our image. Once set, its value won't change. - private final int mImageWidth; - - // The current height in pixels of our image. We will adjust the value when drawing more texts. - private int mImageHeight; - - // The current vertical offset in pixels to draw the top edge of new text strings. - private int mVerticalOffset; - - // The font size to draw the texts. - private final float mFontSize; - - // The name description of the text to localize. It's used to find the translated strings in the - // resource file. - private final String mTextName; - - // The directory that contains all the needed font files (e.g. ttf, otf, ttc files). - private final String mFontDirPath; - - // An explicit map from language to the font name to use. - // The map is extracted from frameworks/base/data/fonts/fonts.xml. - // And the language-subtag-registry is found in: - // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry - private static final String DEFAULT_FONT_NAME = "Roboto-Regular"; - private static final Map<String, String> LANGUAGE_TO_FONT_MAP = new TreeMap<String, String>() {{ - put("am", "NotoSansEthiopic-Regular"); - put("ar", "NotoNaskhArabicUI-Regular"); - put("as", "NotoSansBengaliUI-Regular"); - put("bn", "NotoSansBengaliUI-Regular"); - put("fa", "NotoNaskhArabicUI-Regular"); - put("gu", "NotoSansGujaratiUI-Regular"); - put("hi", "NotoSansDevanagariUI-Regular"); - put("hy", "NotoSansArmenian-Regular"); - put("iw", "NotoSansHebrew-Regular"); - put("ja", "NotoSansCJK-Regular"); - put("ka", "NotoSansGeorgian-Regular"); - put("ko", "NotoSansCJK-Regular"); - put("km", "NotoSansKhmerUI-Regular"); - put("kn", "NotoSansKannadaUI-Regular"); - put("lo", "NotoSansLaoUI-Regular"); - put("ml", "NotoSansMalayalamUI-Regular"); - put("mr", "NotoSansDevanagariUI-Regular"); - put("my", "NotoSansMyanmarUI-Regular"); - put("ne", "NotoSansDevanagariUI-Regular"); - put("or", "NotoSansOriya-Regular"); - put("pa", "NotoSansGurmukhiUI-Regular"); - put("si", "NotoSansSinhala-Regular"); - put("ta", "NotoSansTamilUI-Regular"); - put("te", "NotoSansTeluguUI-Regular"); - put("th", "NotoSansThaiUI-Regular"); - put("ur", "NotoNaskhArabicUI-Regular"); - put("zh", "NotoSansCJK-Regular"); - }}; - - /** - * Exception to indicate the failure to find the translated text strings. - */ - public static class LocalizedStringNotFoundException extends Exception { - public LocalizedStringNotFoundException(String message) { - super(message); + // Initial height of the image to draw. + private static final int INITIAL_HEIGHT = 20000; + + private static final float DEFAULT_FONT_SIZE = 40; + + // This is the canvas we used to draw texts. + private BufferedImage mBufferedImage; + + // The width in pixels of our image. The value will be adjusted once when we calculate the + // maximum width to fit the wrapped text strings. + private int mImageWidth; + + // The current height in pixels of our image. We will adjust the value when drawing more texts. + private int mImageHeight; + + // The current vertical offset in pixels to draw the top edge of new text strings. + private int mVerticalOffset; + + // The font size to draw the texts. + private final float mFontSize; + + // The name description of the text to localize. It's used to find the translated strings in the + // resource file. + private final String mTextName; + + // The directory that contains all the needed font files (e.g. ttf, otf, ttc files). + private final String mFontDirPath; + + // Align the text in the center of the image. + private final boolean mCenterAlignment; + + // An explicit map from language to the font name to use. + // The map is extracted from frameworks/base/data/fonts/fonts.xml. + // And the language-subtag-registry is found in: + // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + private static final String DEFAULT_FONT_NAME = "Roboto-Regular"; + private static final Map<String, String> LANGUAGE_TO_FONT_MAP = + new TreeMap<String, String>() { + { + put("am", "NotoSansEthiopic-Regular"); + put("ar", "NotoNaskhArabicUI-Regular"); + put("as", "NotoSansBengaliUI-Regular"); + put("bn", "NotoSansBengaliUI-Regular"); + put("fa", "NotoNaskhArabicUI-Regular"); + put("gu", "NotoSansGujaratiUI-Regular"); + put("hi", "NotoSansDevanagariUI-Regular"); + put("hy", "NotoSansArmenian-Regular"); + put("iw", "NotoSansHebrew-Regular"); + put("ja", "NotoSansCJK-Regular"); + put("ka", "NotoSansGeorgian-Regular"); + put("ko", "NotoSansCJK-Regular"); + put("km", "NotoSansKhmerUI-Regular"); + put("kn", "NotoSansKannadaUI-Regular"); + put("lo", "NotoSansLaoUI-Regular"); + put("ml", "NotoSansMalayalamUI-Regular"); + put("mr", "NotoSansDevanagariUI-Regular"); + put("my", "NotoSansMyanmarUI-Regular"); + put("ne", "NotoSansDevanagariUI-Regular"); + put("or", "NotoSansOriya-Regular"); + put("pa", "NotoSansGurmukhiUI-Regular"); + put("si", "NotoSansSinhala-Regular"); + put("ta", "NotoSansTamilUI-Regular"); + put("te", "NotoSansTeluguUI-Regular"); + put("th", "NotoSansThaiUI-Regular"); + put("ur", "NotoNaskhArabicUI-Regular"); + put("zh", "NotoSansCJK-Regular"); + } + }; + + // Languages that write from right to left. + private static final Set<String> RTL_LANGUAGE = + new HashSet<String>() { + { + add("ar"); // Arabic + add("fa"); // Persian + add("he"); // Hebrew + add("iw"); // Hebrew + add("ur"); // Urdu + } + }; + + // Languages that breaks on arbitrary characters. + // TODO(xunchang) switch to icu library if possible. For example, for Thai and Khmer, there is + // no space between words; and word breaking is based on grammatical analysis and on word + // matching in dictionaries. + private static final Set<String> LOGOGRAM_LANGUAGE = + new HashSet<String>() { + { + add("ja"); // Japanese + add("km"); // Khmer + add("ko"); // Korean + add("lo"); // Lao + add("th"); // Thai + add("zh"); // Chinese + } + }; + + /** Exception to indicate the failure to find the translated text strings. */ + public static class LocalizedStringNotFoundException extends Exception { + public LocalizedStringNotFoundException(String message) { + super(message); + } + + public LocalizedStringNotFoundException(String message, Throwable cause) { + super(message, cause); + } } - public LocalizedStringNotFoundException(String message, Throwable cause) { - super(message, cause); + /** Initailizes the fields of the image image. */ + public ImageGenerator( + int initialImageWidth, + String textName, + float fontSize, + String fontDirPath, + boolean centerAlignment) { + mImageWidth = initialImageWidth; + mImageHeight = INITIAL_HEIGHT; + mVerticalOffset = 0; + + // Initialize the canvas with the default height. + mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY); + + mTextName = textName; + mFontSize = fontSize; + mFontDirPath = fontDirPath; + + mCenterAlignment = centerAlignment; } - } - - /** - * Initailizes the fields of the image image. - */ - public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) { - mImageWidth = imageWidth; - mImageHeight = INITIAL_HEIGHT; - mVerticalOffset = 0; - - // Initialize the canvas with the default height. - mBufferedImage = new BufferedImage(mImageWidth, mImageHeight, BufferedImage.TYPE_BYTE_GRAY); - - mTextName = textName; - mFontSize = fontSize; - mFontDirPath = fontDirPath; - } - - /** - * Finds the translated text string for the given textName by parsing the resourceFile. - * Example of the xml fields: - * <resources xmlns:android="http://schemas.android.com/apk/res/android"> - * <string name="recovery_installing_security" msgid="9184031299717114342"> - * "Sicherheitsupdate wird installiert"</string> - * </resources> - * - * @param resourceFile the input resource file in xml format. - * @param textName the name description of the text. - * - * @return the string representation of the translated text. - */ - private String getTextString(File resourceFile, String textName) throws IOException, - ParserConfigurationException, org.xml.sax.SAXException, LocalizedStringNotFoundException { - DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = builder.newDocumentBuilder(); - - Document doc = db.parse(resourceFile); - doc.getDocumentElement().normalize(); - - NodeList nodeList = doc.getElementsByTagName("string"); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - String name = node.getAttributes().getNamedItem("name").getNodeValue(); - if (name.equals(textName)) { - return node.getTextContent(); - } + + /** + * Finds the translated text string for the given textName by parsing the resourceFile. Example + * of the xml fields: <resources xmlns:android="http://schemas.android.com/apk/res/android"> + * <string name="recovery_installing_security" msgid="9184031299717114342"> "Sicherheitsupdate + * wird installiert"</string> </resources> + * + * @param resourceFile the input resource file in xml format. + * @param textName the name description of the text. + * @return the string representation of the translated text. + */ + private String getTextString(File resourceFile, String textName) + throws IOException, ParserConfigurationException, org.xml.sax.SAXException, + LocalizedStringNotFoundException { + DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = builder.newDocumentBuilder(); + + Document doc = db.parse(resourceFile); + doc.getDocumentElement().normalize(); + + NodeList nodeList = doc.getElementsByTagName("string"); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + String name = node.getAttributes().getNamedItem("name").getNodeValue(); + if (name.equals(textName)) { + return node.getTextContent(); + } + } + + throw new LocalizedStringNotFoundException( + textName + " not found in " + resourceFile.getName()); } - throw new LocalizedStringNotFoundException(textName + " not found in " - + resourceFile.getName()); - } - - /** - * Constructs the locale from the name of the resource file. - */ - private Locale getLocaleFromFilename(String filename) throws IOException { - // Gets the locale string by trimming the top "values-". - String localeString = filename.substring(7); - if (localeString.matches("[A-Za-z]+")) { - return Locale.forLanguageTag(localeString); + /** Constructs the locale from the name of the resource file. */ + private Locale getLocaleFromFilename(String filename) throws IOException { + // Gets the locale string by trimming the top "values-". + String localeString = filename.substring(7); + if (localeString.matches("[A-Za-z]+")) { + return Locale.forLanguageTag(localeString); + } + if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) { + // "${Language}-r${Region}". e.g. en-rGB + String[] tokens = localeString.split("-r"); + return Locale.forLanguageTag(String.join("-", tokens)); + } + if (localeString.startsWith("b+")) { + // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}" + String[] tokens = localeString.substring(2).split("\\+"); + return Locale.forLanguageTag(String.join("-", tokens)); + } + + throw new IOException("Unrecognized locale string " + localeString); } - if (localeString.matches("[A-Za-z]+-r[A-Za-z]+")) { - // "${Language}-r${Region}". e.g. en-rGB - String[] tokens = localeString.split("-r"); - return Locale.forLanguageTag(String.join("-", tokens)); + + /** + * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource + * directory and collect the translated text. + * + * @param resourcePath the path to the resource directory + * @return a map with the locale as key, and translated text as value + * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given + * locale + */ + public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath) + throws IOException, LocalizedStringNotFoundException { + File resourceDir = new File(resourcePath); + if (!resourceDir.isDirectory()) { + throw new LocalizedStringNotFoundException(resourcePath + " is not a directory."); + } + + Map<Locale, String> result = + // Overrides the string comparator so that sr is sorted behind sr-Latn. And thus + // recovery can find the most relevant locale when going down the list. + new TreeMap<>( + (Locale l1, Locale l2) -> { + if (l1.toLanguageTag().equals(l2.toLanguageTag())) { + return 0; + } + if (l1.getLanguage().equals(l2.toLanguageTag())) { + return -1; + } + if (l2.getLanguage().equals(l1.toLanguageTag())) { + return 1; + } + return l1.toLanguageTag().compareTo(l2.toLanguageTag()); + }); + + // Find all the localized resource subdirectories in the format of values-$LOCALE + String[] nameList = + resourceDir.list((File file, String name) -> name.startsWith("values-")); + for (String name : nameList) { + File textFile = new File(resourcePath, name + "/strings.xml"); + String localizedText; + try { + localizedText = getTextString(textFile, mTextName); + } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) { + throw new LocalizedStringNotFoundException( + "Failed to read the translated text for locale " + name, e); + } + + Locale locale = getLocaleFromFilename(name); + // Removes the double quotation mark from the text. + result.put(locale, localizedText.substring(1, localizedText.length() - 1)); + } + + return result; } - if (localeString.startsWith("b+")) { - // The special case of b+sr+Latn, which has the form "b+${Language}+${ScriptName}" - String[] tokens = localeString.substring(2).split("\\+"); - return Locale.forLanguageTag(String.join("-", tokens)); + + /** + * Returns a font object associated given the given locale + * + * @throws IOException if the font file fails to open + * @throws FontFormatException if the font file doesn't have the expected format + */ + private Font loadFontsByLocale(String language) throws IOException, FontFormatException { + String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME); + String[] suffixes = {".otf", ".ttf", ".ttc"}; + for (String suffix : suffixes) { + File fontFile = new File(mFontDirPath, fontName + suffix); + if (fontFile.isFile()) { + return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize); + } + } + + throw new IOException( + "Can not find the font file " + fontName + " for language " + language); } - throw new IOException("Unrecognized locale string " + localeString); - } - - /** - * Iterates over the xml files in the format of values-$LOCALE/strings.xml under the resource - * directory and collect the translated text. - * - * @param resourcePath the path to the resource directory - * - * @return a map with the locale as key, and translated text as value - * - * @throws LocalizedStringNotFoundException if we cannot find the translated text for the given - * locale - **/ - public Map<Locale, String> readLocalizedStringFromXmls(String resourcePath) throws - IOException, LocalizedStringNotFoundException { - File resourceDir = new File(resourcePath); - if (!resourceDir.isDirectory()) { - throw new LocalizedStringNotFoundException(resourcePath + " is not a directory."); + /** Separates the text string by spaces and wraps it by words. */ + private List<String> wrapTextByWords(String text, FontMetrics metrics) { + List<String> wrappedText = new ArrayList<>(); + StringTokenizer st = new StringTokenizer(text, " \n"); + + StringBuilder line = new StringBuilder(); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (metrics.stringWidth(line + token + " ") > mImageWidth) { + wrappedText.add(line.toString()); + line = new StringBuilder(); + } + line.append(token).append(" "); + } + wrappedText.add(line.toString()); + + return wrappedText; } - Map<Locale, String> result = - new TreeMap<Locale, String>(Comparator.comparing(Locale::toLanguageTag)); - - // Find all the localized resource subdirectories in the format of values-$LOCALE - String[] nameList = resourceDir.list( - (File file, String name) -> name.startsWith("values-")); - for (String name : nameList) { - File textFile = new File(resourcePath, name + "/strings.xml"); - String localizedText; - try { - localizedText = getTextString(textFile, mTextName); - } catch (IOException | ParserConfigurationException | org.xml.sax.SAXException e) { - throw new LocalizedStringNotFoundException( - "Failed to read the translated text for locale " + name, e); - } + /** One character is a word for CJK. */ + private List<String> wrapTextByCharacters(String text, FontMetrics metrics) { + List<String> wrappedText = new ArrayList<>(); + + StringBuilder line = new StringBuilder(); + for (char token : text.toCharArray()) { + if (metrics.stringWidth(line + Character.toString(token)) > mImageWidth) { + wrappedText.add(line.toString()); + line = new StringBuilder(); + } + line.append(token); + } + wrappedText.add(line.toString()); - Locale locale = getLocaleFromFilename(name); - // Removes the double quotation mark from the text. - result.put(locale, localizedText.substring(1, localizedText.length() - 1)); + return wrappedText; } - return result; - } - - /** - * Returns a font object associated given the given locale - * - * @throws IOException if the font file fails to open - * @throws FontFormatException if the font file doesn't have the expected format - */ - private Font loadFontsByLocale(String language) throws IOException, FontFormatException { - String fontName = LANGUAGE_TO_FONT_MAP.getOrDefault(language, DEFAULT_FONT_NAME); - String[] suffixes = {".otf", ".ttf", ".ttc"}; - for (String suffix : suffixes ) { - File fontFile = new File(mFontDirPath, fontName + suffix); - if (fontFile.isFile()) { - return Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(mFontSize); - } + /** + * Wraps the text with a maximum of mImageWidth pixels per line. + * + * @param text the string representation of text to wrap + * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of + * the text given its string representation + * @return a list of strings with their width smaller than mImageWidth pixels + */ + private List<String> wrapText(String text, FontMetrics metrics, String language) { + if (LOGOGRAM_LANGUAGE.contains(language)) { + return wrapTextByCharacters(text, metrics); + } + + return wrapTextByWords(text, metrics); } - throw new IOException("Can not find the font file " + fontName + " for language " + language); - } + /** + * Encodes the information of the text image for |locale|. According to minui/resources.cpp, the + * width, height and locale of the image is decoded as: int w = (row[1] << 8) | row[0]; int h = + * (row[3] << 8) | row[2]; __unused int len = row[4]; char* loc = + * reinterpret_cast<char*>(&row[5]); + */ + private List<Integer> encodeTextInfo(int width, int height, String locale) { + List<Integer> info = + new ArrayList<>( + Arrays.asList( + width & 0xff, + width >> 8, + height & 0xff, + height >> 8, + locale.length())); + + byte[] localeBytes = locale.getBytes(); + for (byte b : localeBytes) { + info.add((int) b); + } + info.add(0); + + return info; + } - /** - * Separates the text string by spaces and wraps it by words. - **/ - private List<String> wrapTextByWords(String text, FontMetrics metrics) { - List<String> wrappedText = new ArrayList<>(); - StringTokenizer st = new StringTokenizer(text, " \n"); + /** Returns Graphics2D object that uses the given locale. */ + private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException { + Graphics2D graphics = mBufferedImage.createGraphics(); + graphics.setColor(Color.WHITE); + graphics.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); + graphics.setFont(loadFontsByLocale(locale.getLanguage())); - StringBuilder line = new StringBuilder(); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (metrics.stringWidth(line + token + " ") > mImageWidth) { - wrappedText.add(line.toString()); - line = new StringBuilder(); - } - line.append(token).append(" "); + return graphics; } - wrappedText.add(line.toString()); - - return wrappedText; - } - - /** - * Wraps the text with a maximum of mImageWidth pixels per line. - * - * @param text the string representation of text to wrap - * @param metrics the metrics of the Font used to draw the text; it gives the width in pixels of - * the text given its string representation - * - * @return a list of strings with their width smaller than mImageWidth pixels - */ - private List<String> wrapText(String text, FontMetrics metrics) { - // TODO(xunchang) handle other cases of text wrapping - // 1. RTL languages: "ar"(Arabic), "fa"(Persian), "he"(Hebrew), "iw"(Hebrew), "ur"(Urdu) - // 2. Language uses characters: CJK, "lo"(lao), "km"(khmer) - - return wrapTextByWords(text, metrics); - } - - /** - * Draws the text string on the canvas for given locale. - * - * @param text the string to draw on canvas - * @param locale the current locale tag of the string to draw - * - * @throws IOException if we cannot find the corresponding font file for the given locale. - * @throws FontFormatException if we failed to load the font file for the given locale. - */ - private void drawText(String text, Locale locale) throws IOException, FontFormatException { - Graphics2D graphics = mBufferedImage.createGraphics(); - graphics.setColor(Color.WHITE); - graphics.setRenderingHint( - RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); - graphics.setFont(loadFontsByLocale(locale.getLanguage())); - - System.out.println("Drawing text for locale " + locale + " text " + text); - - FontMetrics fontMetrics = graphics.getFontMetrics(); - List<String> wrappedText = wrapTextByWords(text, fontMetrics); - for (String line : wrappedText) { - int lineHeight = fontMetrics.getHeight(); - // Doubles the height of the image if we are short of space. - if (mVerticalOffset + lineHeight >= mImageHeight) { - resizeHeight(mImageHeight * 2); - } - - // Draws the text at mVerticalOffset and increments the offset with line space. - int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent(); - graphics.drawString(line, 0, baseLine); - mVerticalOffset += lineHeight; + + /** Returns the maximum screen width needed to fit the given text after wrapping. */ + private int measureTextWidth(String text, Locale locale) + throws IOException, FontFormatException { + Graphics2D graphics = createGraphics(locale); + FontMetrics fontMetrics = graphics.getFontMetrics(); + List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage()); + + int textWidth = 0; + for (String line : wrappedText) { + textWidth = Math.max(textWidth, fontMetrics.stringWidth(line)); + } + + // This may happen if one single word is larger than the image width. + if (textWidth > mImageWidth) { + throw new IllegalStateException( + "Wrapped text width " + + textWidth + + " is larger than image width " + + mImageWidth + + " for locale: " + + locale); + } + + return textWidth; } - } - - /** - * Redraws the image with the new height. - * - * @param height the new height of the image in pixels. - */ - private void resizeHeight(int height) { - BufferedImage resizedImage = - new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY); - Graphics2D graphic = resizedImage.createGraphics(); - graphic.drawImage(mBufferedImage, 0, 0, null); - graphic.dispose(); - - mBufferedImage = resizedImage; - mImageHeight = height; - } - - /** - * This function draws the font characters and saves the result to outputPath. - * - * @param localizedTextMap a map from locale to its translated text string - * @param outputPath the path to write the generated image file. - * - * @throws FontFormatException if there's a format error in one of the font file - * @throws IOException if we cannot find the font file for one of the locale, or we failed to - * write the image file. - */ - public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) throws - FontFormatException, IOException { - for (Locale locale : localizedTextMap.keySet()) { - // TODO(xunchang) reprocess the locales for the same language and make the last locale the - // catch-all type. e.g. "zh-CN, zh-HK, zh-TW" will become "zh-CN, zh-HK, zh" - // Or maybe we don't need to support these variants? - drawText(localizedTextMap.get(locale), locale); + + /** + * Draws the text string on the canvas for given locale. + * + * @param text the string to draw on canvas + * @param locale the current locale tag of the string to draw + * @throws IOException if we cannot find the corresponding font file for the given locale. + * @throws FontFormatException if we failed to load the font file for the given locale. + */ + private void drawText(String text, Locale locale, String languageTag) + throws IOException, FontFormatException { + System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text); + + Graphics2D graphics = createGraphics(locale); + FontMetrics fontMetrics = graphics.getFontMetrics(); + List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage()); + + // Marks the start y offset for the text image of current locale; and reserves one line to + // encode the image metadata. + int currentImageStart = mVerticalOffset; + mVerticalOffset += 1; + for (String line : wrappedText) { + int lineHeight = fontMetrics.getHeight(); + // Doubles the height of the image if we are short of space. + if (mVerticalOffset + lineHeight >= mImageHeight) { + resize(mImageWidth, mImageHeight * 2); + } + + // Draws the text at mVerticalOffset and increments the offset with line space. + int baseLine = mVerticalOffset + lineHeight - fontMetrics.getDescent(); + + // Draws from right if it's an RTL language. + int x = + mCenterAlignment + ? (mImageWidth - fontMetrics.stringWidth(line)) / 2 + : RTL_LANGUAGE.contains(languageTag) + ? mImageWidth - fontMetrics.stringWidth(line) + : 0; + + graphics.drawString(line, x, baseLine); + + mVerticalOffset += lineHeight; + } + + // Encodes the metadata of the current localized image as pixels. + int currentImageHeight = mVerticalOffset - currentImageStart - 1; + List<Integer> info = encodeTextInfo(mImageWidth, currentImageHeight, languageTag); + for (int i = 0; i < info.size(); i++) { + int[] pixel = {info.get(i)}; + mBufferedImage.getRaster().setPixel(i, currentImageStart, pixel); + } } - // TODO(xunchang) adjust the width to save some space if all texts are smaller than imageWidth. - resizeHeight(mVerticalOffset); - ImageIO.write(mBufferedImage, "png", new File(outputPath)); - } - - public static void printUsage() { - System.out.println("Usage: java -jar path_to_jar imageWidth textName fontDirectory" - + " resourceDirectory outputFilename"); - } - - public static void main(String[] args) throws NumberFormatException, IOException, - FontFormatException, LocalizedStringNotFoundException { - if (args.length != 5) { - printUsage(); - System.err.println("We expect 5 arguments, get " + args.length); - System.exit(1); + /** + * Redraws the image with the new width and new height. + * + * @param width the new width of the image in pixels. + * @param height the new height of the image in pixels. + */ + private void resize(int width, int height) { + BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + Graphics2D graphic = resizedImage.createGraphics(); + graphic.drawImage(mBufferedImage, 0, 0, null); + graphic.dispose(); + + mBufferedImage = resizedImage; + mImageWidth = width; + mImageHeight = height; } - // TODO(xunchang) switch to commandline parser - int imageWidth = Integer.parseUnsignedInt(args[0]); + /** + * This function draws the font characters and saves the result to outputPath. + * + * @param localizedTextMap a map from locale to its translated text string + * @param outputPath the path to write the generated image file. + * @throws FontFormatException if there's a format error in one of the font file + * @throws IOException if we cannot find the font file for one of the locale, or we failed to + * write the image file. + */ + public void generateImage(Map<Locale, String> localizedTextMap, String outputPath) + throws FontFormatException, IOException { + Map<String, Integer> languageCount = new TreeMap<>(); + int textWidth = 0; + for (Locale locale : localizedTextMap.keySet()) { + String language = locale.getLanguage(); + languageCount.put(language, languageCount.getOrDefault(language, 0) + 1); + textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale)); + } + + // Removes the black margins to reduce the size of the image. + resize(textWidth, mImageHeight); + + for (Locale locale : localizedTextMap.keySet()) { + Integer count = languageCount.get(locale.getLanguage()); + // Recovery expects en-US instead of en_US. + String languageTag = locale.toLanguageTag(); + if (count == 1) { + // Make the last country variant for a given language be the catch-all for that + // language. + languageTag = locale.getLanguage(); + } else { + languageCount.put(locale.getLanguage(), count - 1); + } + + drawText(localizedTextMap.get(locale), locale, languageTag); + } + + resize(mImageWidth, mVerticalOffset); + ImageIO.write(mBufferedImage, "png", new File(outputPath)); + } - ImageGenerator imageGenerator = - new ImageGenerator(imageWidth, args[1], DEFAULT_FONT_SIZE, args[2]); + /** Prints the helper message. */ + public static void printUsage(Options options) { + new HelpFormatter().printHelp("java -jar path_to_jar [required_options]", options); + } - Map<Locale, String> localizedStringMap = - imageGenerator.readLocalizedStringFromXmls(args[3]); - imageGenerator.generateImage(localizedStringMap, args[4]); - } -} + /** Creates the command line options. */ + public static Options createOptions() { + Options options = new Options(); + options.addOption( + OptionBuilder.withLongOpt("image_width") + .withDescription("The initial width of the image in pixels.") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("text_name") + .withDescription( + "The description of the text string, e.g. recovery_erasing") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("font_dir") + .withDescription( + "The directory that contains all the support font format files, " + + "e.g. $OUT/system/fonts/") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("resource_dir") + .withDescription( + "The resource directory that contains all the translated strings in" + + " xml format, e.g." + + " bootable/recovery/tools/recovery_l10n/res/") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("output_file") + .withDescription("Path to the generated image.") + .hasArgs(1) + .isRequired() + .create()); + + options.addOption( + OptionBuilder.withLongOpt("center_alignment") + .withDescription("Align the text in the center of the screen.") + .hasArg(false) + .create()); + + return options; + } + /** The main function parses the command line options and generates the desired text image. */ + public static void main(String[] args) + throws NumberFormatException, IOException, FontFormatException, + LocalizedStringNotFoundException { + Options options = createOptions(); + CommandLine cmd; + try { + cmd = new GnuParser().parse(options, args); + } catch (ParseException e) { + System.err.println(e.getMessage()); + printUsage(options); + return; + } + + int imageWidth = Integer.parseUnsignedInt(cmd.getOptionValue("image_width")); + + ImageGenerator imageGenerator = + new ImageGenerator( + imageWidth, + cmd.getOptionValue("text_name"), + DEFAULT_FONT_SIZE, + cmd.getOptionValue("font_dir"), + cmd.hasOption("center_alignment")); + + Map<Locale, String> localizedStringMap = + imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir")); + imageGenerator.generateImage(localizedStringMap, cmd.getOptionValue("output_file")); + } +} diff --git a/tools/image_generator/README.md b/tools/image_generator/README.md index 22e32f6ce..5d70354e4 100644 --- a/tools/image_generator/README.md +++ b/tools/image_generator/README.md @@ -6,7 +6,8 @@ under recovery mode. And thus we don't need to do the manual work by running emulators with different dpi. # Usage: - `java -jar path_to_jar imageWidth textName fontDirectory resourceDirectory outputFilename` + `java -jar path_to_jar --image_width imageWidth --text_name textName --font_dir fontDirectory + --resource_dir resourceDirectory --output_file outputFilename` # Description of the parameters: 1. `imageWidth`: The number of pixels per line; and the text strings will be diff --git a/tools/recovery_l10n/res/values/strings.xml b/tools/recovery_l10n/res/values/strings.xml index d56d0733c..a557ba8af 100644 --- a/tools/recovery_l10n/res/values/strings.xml +++ b/tools/recovery_l10n/res/values/strings.xml @@ -36,4 +36,36 @@ system is installing a security update. [CHAR LIMIT=60] --> <string name="recovery_installing_security">Installing security update</string> + <!-- Displayed on the screen beneath the recovery titles when the + device enters the recovery mode and prompts a data wipe. [CHAR + LIMIT=400] --> + <string name="recovery_wipe_data_menu_header">Cannot load Android + system. Your data may be corrupt. If you continue to get this + message, you may need to perform a factory data reset and erase + all user data stored on this device.</string> + + <!-- Displayed on the screen as the first element of the menu to + prompt the wipe data, beneath the menu header. The menu shows + up when the device enters the recovery mode and prompts a data + wipe. [CHAR LIMIT=60] --> + <string name="recovery_try_again">Try again</string> + + <!-- Displayed on the screen as the second element of the menu to + prompt the wipe data, beneath the menu header. The menu shows + up when the device enters the recovery mode and prompts a data + wipe. [CHAR LIMIT=60] --> + <string name="recovery_factory_data_reset">Factory data reset</string> + + <!-- Displayed on the screen beneath the recovery titles when users + select "Factory data reset" in the previous menu. [CHAR + LIMIT=150] --> + <string name="recovery_wipe_data_confirmation">Wipe all user data?\n\n + THIS CAN NOT BE UNDONE!</string> + + <!-- Displayed on the screen as the first element of the wipe data + confirmation menu. The menu shows up when users select + "Factory data reset" when prompted to wipe data. [CHAR + LIMIT=60] --> + <string name="recovery_cancel_wipe_data">Cancel</string> + </resources> diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp index 47849a155..c4c09098e 100644 --- a/updater/blockimg.cpp +++ b/updater/blockimg.cpp @@ -1399,7 +1399,10 @@ static int PerformCommandDiff(CommandParameters& params) { // We expect the output of the patcher to fill the tgt ranges exactly. if (!writer.Finished()) { - LOG(ERROR) << "range sink underrun?"; + LOG(ERROR) << "Failed to fully write target blocks (range sink underrun): Missing " + << writer.AvailableSpace() << " bytes"; + failure_type = kPatchApplicationFailure; + return -1; } } else { LOG(INFO) << "skipping " << blocks << " blocks already patched to " << tgt.blocks() << " [" diff --git a/verifier.cpp b/verifier.cpp index 2101dcb41..44bd4e180 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -308,144 +308,6 @@ int verify_file(const unsigned char* addr, size_t length, const std::vector<Cert return VERIFY_FAILURE; } -std::unique_ptr<RSA, RSADeleter> parse_rsa_key(FILE* file, uint32_t exponent) { - // Read key length in words and n0inv. n0inv is a precomputed montgomery - // parameter derived from the modulus and can be used to speed up - // verification. n0inv is 32 bits wide here, assuming the verification logic - // uses 32 bit arithmetic. However, BoringSSL may use a word size of 64 bits - // internally, in which case we don't have a valid n0inv. Thus, we just - // ignore the montgomery parameters and have BoringSSL recompute them - // internally. If/When the speedup from using the montgomery parameters - // becomes relevant, we can add more sophisticated code here to obtain a - // 64-bit n0inv and initialize the montgomery parameters in the key object. - uint32_t key_len_words = 0; - uint32_t n0inv = 0; - if (fscanf(file, " %i , 0x%x", &key_len_words, &n0inv) != 2) { - return nullptr; - } - - if (key_len_words > 8192 / 32) { - LOG(ERROR) << "key length (" << key_len_words << ") too large"; - return nullptr; - } - - // Read the modulus. - std::unique_ptr<uint32_t[]> modulus(new uint32_t[key_len_words]); - if (fscanf(file, " , { %u", &modulus[0]) != 1) { - return nullptr; - } - for (uint32_t i = 1; i < key_len_words; ++i) { - if (fscanf(file, " , %u", &modulus[i]) != 1) { - return nullptr; - } - } - - // Cconvert from little-endian array of little-endian words to big-endian - // byte array suitable as input for BN_bin2bn. - std::reverse((uint8_t*)modulus.get(), - (uint8_t*)(modulus.get() + key_len_words)); - - // The next sequence of values is the montgomery parameter R^2. Since we - // generally don't have a valid |n0inv|, we ignore this (see comment above). - uint32_t rr_value; - if (fscanf(file, " } , { %u", &rr_value) != 1) { - return nullptr; - } - for (uint32_t i = 1; i < key_len_words; ++i) { - if (fscanf(file, " , %u", &rr_value) != 1) { - return nullptr; - } - } - if (fscanf(file, " } } ") != 0) { - return nullptr; - } - - // Initialize the key. - std::unique_ptr<RSA, RSADeleter> key(RSA_new()); - if (!key) { - return nullptr; - } - - key->n = BN_bin2bn((uint8_t*)modulus.get(), - key_len_words * sizeof(uint32_t), NULL); - if (!key->n) { - return nullptr; - } - - key->e = BN_new(); - if (!key->e || !BN_set_word(key->e, exponent)) { - return nullptr; - } - - return key; -} - -struct BNDeleter { - void operator()(BIGNUM* bn) const { - BN_free(bn); - } -}; - -std::unique_ptr<EC_KEY, ECKEYDeleter> parse_ec_key(FILE* file) { - uint32_t key_len_bytes = 0; - if (fscanf(file, " %i", &key_len_bytes) != 1) { - return nullptr; - } - - std::unique_ptr<EC_GROUP, void (*)(EC_GROUP*)> group( - EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), EC_GROUP_free); - if (!group) { - return nullptr; - } - - // Verify that |key_len| matches the group order. - if (key_len_bytes != BN_num_bytes(EC_GROUP_get0_order(group.get()))) { - return nullptr; - } - - // Read the public key coordinates. Note that the byte order in the file is - // little-endian, so we convert to big-endian here. - std::unique_ptr<uint8_t[]> bytes(new uint8_t[key_len_bytes]); - std::unique_ptr<BIGNUM, BNDeleter> point[2]; - for (int i = 0; i < 2; ++i) { - unsigned int byte = 0; - if (fscanf(file, " , { %u", &byte) != 1) { - return nullptr; - } - bytes[key_len_bytes - 1] = byte; - - for (size_t i = 1; i < key_len_bytes; ++i) { - if (fscanf(file, " , %u", &byte) != 1) { - return nullptr; - } - bytes[key_len_bytes - i - 1] = byte; - } - - point[i].reset(BN_bin2bn(bytes.get(), key_len_bytes, nullptr)); - if (!point[i]) { - return nullptr; - } - - if (fscanf(file, " }") != 0) { - return nullptr; - } - } - - if (fscanf(file, " } ") != 0) { - return nullptr; - } - - // Create and initialize the key. - std::unique_ptr<EC_KEY, ECKEYDeleter> key(EC_KEY_new()); - if (!key || !EC_KEY_set_group(key.get(), group.get()) || - !EC_KEY_set_public_key_affine_coordinates(key.get(), point[0].get(), - point[1].get())) { - return nullptr; - } - - return key; -} - static std::vector<Certificate> IterateZipEntriesAndSearchForKeys(const ZipArchiveHandle& handle) { void* cookie; ZipString suffix("x509.pem"); @@ -500,6 +362,48 @@ std::vector<Certificate> LoadKeysFromZipfile(const std::string& zip_name) { return result; } +bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa) { + if (!rsa) { + return false; + } + + const BIGNUM* out_n; + const BIGNUM* out_e; + RSA_get0_key(rsa.get(), &out_n, &out_e, nullptr /* private exponent */); + auto modulus_bits = BN_num_bits(out_n); + if (modulus_bits != 2048) { + LOG(ERROR) << "Modulus should be 2048 bits long, actual: " << modulus_bits; + return false; + } + + BN_ULONG exponent = BN_get_word(out_e); + if (exponent != 3 && exponent != 65537) { + LOG(ERROR) << "Public exponent should be 3 or 65537, actual: " << exponent; + return false; + } + + return true; +} + +bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key) { + if (!ec_key) { + return false; + } + + const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key.get()); + if (!ec_group) { + LOG(ERROR) << "Failed to get the ec_group from the ec_key"; + return false; + } + auto degree = EC_GROUP_get_degree(ec_group); + if (degree != 256) { + LOG(ERROR) << "Field size of the ec key should be 256 bits long, actual: " << degree; + return false; + } + + return true; +} + bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certificate* cert) { std::unique_ptr<BIO, decltype(&BIO_free)> content( BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free); @@ -538,22 +442,20 @@ bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certific } int key_type = EVP_PKEY_id(public_key.get()); - // TODO(xunchang) check the rsa key has exponent 3 or 65537 with RSA_get0_key; and ec key is - // 256 bits. if (key_type == EVP_PKEY_RSA) { cert->key_type = Certificate::KEY_TYPE_RSA; cert->ec.reset(); cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get())); - if (!cert->rsa) { - LOG(ERROR) << "Failed to get the rsa key info from public key"; + if (!cert->rsa || !CheckRSAKey(cert->rsa)) { + LOG(ERROR) << "Failed to validate the rsa key info from public key"; return false; } } else if (key_type == EVP_PKEY_EC) { cert->key_type = Certificate::KEY_TYPE_EC; cert->rsa.reset(); cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get())); - if (!cert->ec) { - LOG(ERROR) << "Failed to get the ec key info from the public key"; + if (!cert->ec || !CheckECKey(cert->ec)) { + LOG(ERROR) << "Failed to validate the ec key info from the public key"; return false; } } else { @@ -563,114 +465,3 @@ bool LoadCertificateFromBuffer(const std::vector<uint8_t>& pem_content, Certific return true; } - -// Reads a file containing one or more public keys as produced by -// DumpPublicKey: this is an RSAPublicKey struct as it would appear -// as a C source literal, eg: -// -// "{64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" -// -// For key versions newer than the original 2048-bit e=3 keys -// supported by Android, the string is preceded by a version -// identifier, eg: -// -// "v2 {64,0xc926ad21,{1795090719,...,-695002876},{-857949815,...,1175080310}}" -// -// (Note that the braces and commas in this example are actual -// characters the parser expects to find in the file; the ellipses -// indicate more numbers omitted from this example.) -// -// The file may contain multiple keys in this format, separated by -// commas. The last key must not be followed by a comma. -// -// A Certificate is a pair of an RSAPublicKey and a particular hash -// (we support SHA-1 and SHA-256; we store the hash length to signify -// which is being used). The hash used is implied by the version number. -// -// 1: 2048-bit RSA key with e=3 and SHA-1 hash -// 2: 2048-bit RSA key with e=65537 and SHA-1 hash -// 3: 2048-bit RSA key with e=3 and SHA-256 hash -// 4: 2048-bit RSA key with e=65537 and SHA-256 hash -// 5: 256-bit EC key using the NIST P-256 curve parameters and SHA-256 hash -// -// Returns true on success, and appends the found keys (at least one) to certs. -// Otherwise returns false if the file failed to parse, or if it contains zero -// keys. The contents in certs would be unspecified on failure. -bool load_keys(const char* filename, std::vector<Certificate>& certs) { - std::unique_ptr<FILE, decltype(&fclose)> f(fopen(filename, "re"), fclose); - if (!f) { - PLOG(ERROR) << "error opening " << filename; - return false; - } - - while (true) { - certs.emplace_back(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); - Certificate& cert = certs.back(); - uint32_t exponent = 0; - - char start_char; - if (fscanf(f.get(), " %c", &start_char) != 1) return false; - if (start_char == '{') { - // a version 1 key has no version specifier. - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 3; - cert.hash_len = SHA_DIGEST_LENGTH; - } else if (start_char == 'v') { - int version; - if (fscanf(f.get(), "%d {", &version) != 1) return false; - switch (version) { - case 2: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 65537; - cert.hash_len = SHA_DIGEST_LENGTH; - break; - case 3: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 3; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - case 4: - cert.key_type = Certificate::KEY_TYPE_RSA; - exponent = 65537; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - case 5: - cert.key_type = Certificate::KEY_TYPE_EC; - cert.hash_len = SHA256_DIGEST_LENGTH; - break; - default: - return false; - } - } - - if (cert.key_type == Certificate::KEY_TYPE_RSA) { - cert.rsa = parse_rsa_key(f.get(), exponent); - if (!cert.rsa) { - return false; - } - - LOG(INFO) << "read key e=" << exponent << " hash=" << cert.hash_len; - } else if (cert.key_type == Certificate::KEY_TYPE_EC) { - cert.ec = parse_ec_key(f.get()); - if (!cert.ec) { - return false; - } - } else { - LOG(ERROR) << "Unknown key type " << cert.key_type; - return false; - } - - // if the line ends in a comma, this file has more keys. - int ch = fgetc(f.get()); - if (ch == ',') { - // more keys to come. - continue; - } else if (ch == EOF) { - break; - } else { - LOG(ERROR) << "unexpected character between keys"; - return false; - } - } - return true; -} diff --git a/verifier.h b/verifier.h index b7924c71f..df9a4b648 100644 --- a/verifier.h +++ b/verifier.h @@ -70,7 +70,11 @@ struct Certificate { int verify_file(const unsigned char* addr, size_t length, const std::vector<Certificate>& keys, const std::function<void(float)>& set_progress = nullptr); -bool load_keys(const char* filename, std::vector<Certificate>& certs); +// Checks that the RSA key has a modulus of 2048 bits long, and public exponent is 3 or 65537. +bool CheckRSAKey(const std::unique_ptr<RSA, RSADeleter>& rsa); + +// Checks that the field size of the curve for the EC key is 256 bits. +bool CheckECKey(const std::unique_ptr<EC_KEY, ECKEYDeleter>& ec_key); // Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns // false if there is a parsing failure or the signature's encryption algorithm is not supported. diff --git a/wear_ui.cpp b/wear_ui.cpp index 0611f94c9..6da84c924 100644 --- a/wear_ui.cpp +++ b/wear_ui.cpp @@ -51,8 +51,8 @@ void WearRecoveryUI::draw_background_locked() { gr_color(0, 0, 0, 255); gr_fill(0, 0, gr_fb_width(), gr_fb_height()); - if (currentIcon != NONE) { - GRSurface* frame = GetCurrentFrame(); + if (current_icon_ != NONE) { + const auto& frame = GetCurrentFrame(); int frame_width = gr_get_width(frame); int frame_height = gr_get_height(frame); int frame_x = (gr_fb_width() - frame_width) / 2; @@ -60,7 +60,7 @@ void WearRecoveryUI::draw_background_locked() { gr_blit(frame, 0, 0, frame_width, frame_height, frame_x, frame_y); // Draw recovery text on screen above progress bar. - GRSurface* text = GetCurrentText(); + const auto& text = GetCurrentText(); int text_x = (ScreenWidth() - gr_get_width(text)) / 2; int text_y = GetProgressBaseline() - gr_get_height(text) - 10; gr_color(255, 255, 255, 255); |