/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_LIBDEXFILE_DEX_DEX_FILE_LOADER_H_
#define ART_LIBDEXFILE_DEX_DEX_FILE_LOADER_H_

#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/os.h"
#include "base/unix_file/fd_file.h"
#include "dex_file.h"

namespace art {

class MemMap;
class OatDexFile;
class ZipArchive;

enum class DexFileLoaderErrorCode {
  kNoError,
  kEntryNotFound,
  kExtractToMemoryError,
  kDexFileError,
  kMakeReadOnlyError,
  kVerifyError
};

// Class that is used to open dex files and deal with corresponding multidex and location logic.
class DexFileLoader {
 public:
  // name of the DexFile entry within a zip archive
  static constexpr const char* kClassesDex = "classes.dex";

  // The separator character in MultiDex locations.
  static constexpr char kMultiDexSeparator = '!';

  // Return true if the magic is valid for dex or cdex.
  static bool IsMagicValid(uint32_t magic);
  static bool IsMagicValid(const uint8_t* magic);

  // Return true if the corresponding version and magic is valid.
  static bool IsVersionAndMagicValid(const uint8_t* magic);

  // Check whether a location denotes a multidex dex file. This is a very simple check: returns
  // whether the string contains the separator character.
  static bool IsMultiDexLocation(std::string_view location);

  // Return the name of the index-th classes.dex in a multidex zip file. This is classes.dex for
  // index == 0, and classes{index + 1}.dex else.
  static std::string GetMultiDexClassesDexName(size_t index);

  // Return the (possibly synthetic) dex location for a multidex entry. This is dex_location for
  // index == 0, and dex_location + multi-dex-separator + GetMultiDexClassesDexName(index) else.
  static std::string GetMultiDexLocation(size_t index, const char* dex_location);

  // Returns the multidex location and the checksum for each dex file in a zip or a dex container.
  //
  // This uses the source path provided to DexFileLoader constructor.
  //
  // Returns false on error.
  bool GetMultiDexChecksums(/*out*/ std::vector<std::pair<std::string, uint32_t>>* checksums,
                            /*out*/ std::string* error_msg,
                            /*out*/ bool* only_contains_uncompressed_dex = nullptr);

  // Returns combined checksum of one or more dex files (one checksum for the whole multidex set).
  //
  // This uses the source path provided to DexFileLoader constructor.
  //
  // Returns false on error.  Sets *checksum to nullopt for an empty set.
  bool GetMultiDexChecksum(/*out*/ std::optional<uint32_t>* checksum,
                           /*out*/ std::string* error_msg,
                           /*out*/ bool* only_contains_uncompressed_dex = nullptr);

  // Returns combined checksum of one or more dex files (one checksum for the whole multidex set).
  //
  // This uses already open dex files.
  //
  // It starts iteration at index 'i', which must be a primary dex file,
  // and it sets 'i' to the next primary dex file or to end of the array.
  template <typename DexFilePtrVector>  // array|vector<unique_ptr|DexFile|OatDexFile*>.
  static uint32_t GetMultiDexChecksum(const DexFilePtrVector& dex_files,
                                      /*inout*/ size_t* i) {
    CHECK_LT(*i, dex_files.size()) << "No dex files";
    std::optional<uint32_t> checksum;
    for (; *i < dex_files.size(); ++(*i)) {
      const auto* dex_file = &*dex_files[*i];
      bool is_primary_dex = !IsMultiDexLocation(dex_file->GetLocation());
      if (!checksum.has_value()) {                         // First dex file.
        CHECK(is_primary_dex) << dex_file->GetLocation();  // Expect primary dex.
      } else if (is_primary_dex) {                         // Later dex file.
        break;  // Found another primary dex file, terminate iteration.
      }
      if (!is_primary_dex && dex_file->GetDexVersion() >= DexFile::kDexContainerVersion) {
        if (dex_file->GetLocationChecksum() == dex_files[*i - 1]->GetLocationChecksum() + 1) {
          continue;
        }
      }
      checksum = checksum.value_or(kEmptyMultiDexChecksum) ^ dex_file->GetLocationChecksum();
    }
    CHECK(checksum.has_value());
    return checksum.value();
  }

  // Calculate checksum of dex files in a vector, starting at index 0.
  // It will CHECK that the whole vector is consumed (i.e. there is just one primary dex file).
  template <typename DexFilePtrVector>
  static uint32_t GetMultiDexChecksum(const DexFilePtrVector& dex_files) {
    size_t i = 0;
    uint32_t checksum = GetMultiDexChecksum(dex_files, &i);
    CHECK_EQ(i, dex_files.size());
    return checksum;
  }

  // Non-zero initial value for multi-dex to catch bugs if multi-dex checksum is compared
  // directly to DexFile::GetLocationChecksum without going through GetMultiDexChecksum.
  static constexpr uint32_t kEmptyMultiDexChecksum = 1;

  // Returns the canonical form of the given dex location.
  //
  // There are different flavors of "dex locations" as follows:
  // the file name of a dex file:
  //     The actual file path that the dex file has on disk.
  // dex_location:
  //     This acts as a key for the class linker to know which dex file to load.
  //     It may correspond to either an old odex file or a particular dex file
  //     inside an oat file. In the first case it will also match the file name
  //     of the dex file. In the second case (oat) it will include the file name
  //     and possibly some multidex annotation to uniquely identify it.
  // canonical_dex_location:
  //     the dex_location where its file name part has been made canonical.
  static std::string GetDexCanonicalLocation(const char* dex_location);

  // For normal dex files, location and base location coincide. If a dex file is part of a multidex
  // archive, the base location is the name of the originating jar/apk, stripped of any internal
  // classes*.dex path.
  static std::string GetBaseLocation(const char* location) {
    const char* pos = strrchr(location, kMultiDexSeparator);
    return (pos == nullptr) ? location : std::string(location, pos - location);
  }

  static std::string GetBaseLocation(const std::string& location) {
    return GetBaseLocation(location.c_str());
  }

  // Returns the '!classes*.dex' part of the dex location. Returns an empty
  // string if there is no multidex suffix for the given location.
  // The kMultiDexSeparator is included in the returned suffix.
  static std::string GetMultiDexSuffix(const std::string& location) {
    size_t pos = location.rfind(kMultiDexSeparator);
    return (pos == std::string::npos) ? std::string() : location.substr(pos);
  }

  DexFileLoader(const char* filename, const File* file, const std::string& location)
      : filename_(filename), file_(file), location_(location) {
    CHECK(file != nullptr);  // Must be non-null, but may be invalid.
  }

  DexFileLoader(std::shared_ptr<DexFileContainer> container, const std::string& location)
      : root_container_(std::move(container)), location_(location) {
    CHECK(root_container_ != nullptr);
  }

  DexFileLoader(const uint8_t* base, size_t size, const std::string& location);

  DexFileLoader(std::vector<uint8_t>&& memory, const std::string& location);

  DexFileLoader(MemMap&& mem_map, const std::string& location);

  DexFileLoader(File* file, const std::string& location)
      : DexFileLoader(/*filename=*/location.c_str(), file, location) {}

  DexFileLoader(const char* filename, const std::string& location)
      : DexFileLoader(filename, /*file=*/&kInvalidFile, location) {}

  explicit DexFileLoader(const std::string& location)
      : DexFileLoader(location.c_str(), /*file=*/&kInvalidFile, location) {}

  virtual ~DexFileLoader() {}

  // Open singe dex file at the given offset within the container (usually 0).
  // This intentionally ignores all other dex files in the container
  std::unique_ptr<const DexFile> OpenOne(size_t header_offset,
                                         uint32_t location_checksum,
                                         const OatDexFile* oat_dex_file,
                                         bool verify,
                                         bool verify_checksum,
                                         std::string* error_msg);

  // Open single dex file (starting at offset 0 of the container).
  // It expects only single dex file to be present and will fail otherwise.
  std::unique_ptr<const DexFile> Open(uint32_t location_checksum,
                                      const OatDexFile* oat_dex_file,
                                      bool verify,
                                      bool verify_checksum,
                                      std::string* error_msg) {
    std::unique_ptr<const DexFile> dex_file = OpenOne(
        /*header_offset=*/0, location_checksum, oat_dex_file, verify, verify_checksum, error_msg);
    // This API returns only singe DEX file, so check there is just single dex in the container.
    CHECK(dex_file == nullptr || dex_file->IsDexContainerLastEntry()) << location_;
    return dex_file;
  }

  std::unique_ptr<const DexFile> Open(uint32_t location_checksum,
                                      bool verify,
                                      bool verify_checksum,
                                      std::string* error_msg) {
    return Open(location_checksum,
                /*oat_dex_file=*/nullptr,
                verify,
                verify_checksum,
                error_msg);
  }

  // Opens all dex files, guessing the container format based on file magic.
  bool Open(bool verify,
            bool verify_checksum,
            bool allow_no_dex_files,
            DexFileLoaderErrorCode* error_code,
            std::string* error_msg,
            std::vector<std::unique_ptr<const DexFile>>* dex_files);

  bool Open(bool verify,
            bool verify_checksum,
            DexFileLoaderErrorCode* error_code,
            std::string* error_msg,
            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
    return Open(verify,
                verify_checksum,
                /*allow_no_dex_files=*/false,
                error_code,
                error_msg,
                dex_files);
  }

  bool Open(bool verify,
            bool verify_checksum,
            bool allow_no_dex_files,
            std::string* error_msg,
            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
    DexFileLoaderErrorCode error_code;
    return Open(verify, verify_checksum, allow_no_dex_files, &error_code, error_msg, dex_files);
  }

  bool Open(bool verify,
            bool verify_checksum,
            std::string* error_msg,
            std::vector<std::unique_ptr<const DexFile>>* dex_files) {
    DexFileLoaderErrorCode error_code;
    return Open(verify,
                verify_checksum,
                /*allow_no_dex_files=*/false,
                &error_code,
                error_msg,
                dex_files);
  }

 protected:
  static const File kInvalidFile;  // Used for "no file descriptor" (-1).

  bool InitAndReadMagic(size_t header_offset, uint32_t* magic, std::string* error_msg);

  // Ensure we have root container.  If we are backed by a file, memory-map it.
  // We can only do this for dex files since zip files might be too big to map.
  bool MapRootContainer(std::string* error_msg);

  static std::unique_ptr<DexFile> OpenCommon(std::shared_ptr<DexFileContainer> container,
                                             const uint8_t* base,
                                             size_t size,
                                             const std::string& location,
                                             std::optional<uint32_t> location_checksum,
                                             const OatDexFile* oat_dex_file,
                                             bool verify,
                                             bool verify_checksum,
                                             std::string* error_msg,
                                             DexFileLoaderErrorCode* error_code);

  // Old signature preserved for app-compat.
  std::unique_ptr<const DexFile> Open(const uint8_t* base,
                                      size_t size,
                                      const std::string& location,
                                      uint32_t location_checksum,
                                      const OatDexFile* oat_dex_file,
                                      bool verify,
                                      bool verify_checksum,
                                      std::string* error_msg,
                                      std::unique_ptr<DexFileContainer> container) const;

  // Old signature preserved for app-compat.
  enum VerifyResult {};
  static std::unique_ptr<DexFile> OpenCommon(const uint8_t* base,
                                             size_t size,
                                             const uint8_t* data_base,
                                             size_t data_size,
                                             const std::string& location,
                                             uint32_t location_checksum,
                                             const OatDexFile* oat_dex_file,
                                             bool verify,
                                             bool verify_checksum,
                                             std::string* error_msg,
                                             std::unique_ptr<DexFileContainer> container,
                                             VerifyResult* verify_result);

  // Open .dex files from the entry_name in a zip archive.
  bool OpenFromZipEntry(const ZipArchive& zip_archive,
                        const char* entry_name,
                        const std::string& location,
                        bool verify,
                        bool verify_checksum,
                        /*inout*/ size_t* multidex_count,
                        /*out*/ DexFileLoaderErrorCode* error_code,
                        /*out*/ std::string* error_msg,
                        /*out*/ std::vector<std::unique_ptr<const DexFile>>* dex_files) const;

  // The DexFileLoader can be backed either by file or by memory (i.e. DexFileContainer).
  // We can not just mmap the file since APKs might be unreasonably large for 32-bit system.
  std::string filename_;
  const File* file_ = &kInvalidFile;
  std::optional<File> owned_file_;  // May be used as backing storage for 'file_'.
  std::shared_ptr<DexFileContainer> root_container_;

  // The full absolute path to the dex file, if it was loaded from disk.
  //
  // Can also be a path to a multidex container (typically apk), followed by
  // kMultiDexSeparator and the file inside the container.
  //
  // On host this may not be an absolute path.
  //
  // On device libnativeloader uses this to determine the location of the java
  // package or shared library, which decides where to load native libraries
  // from.
  const std::string location_;
};

}  // namespace art

#endif  // ART_LIBDEXFILE_DEX_DEX_FILE_LOADER_H_
