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

#ifndef ART_RUNTIME_VDEX_FILE_H_
#define ART_RUNTIME_VDEX_FILE_H_

#include <stdint.h>
#include <string>

#include "base/array_ref.h"
#include "base/macros.h"
#include "base/mem_map.h"
#include "base/os.h"
#include "class_status.h"
#include "dex/compact_offset_table.h"
#include "dex/dex_file.h"
#include "quicken_info.h"
#include "handle.h"

namespace art {

class ClassLoaderContext;
class Thread;

namespace mirror {
class Class;
}

namespace verifier {
class VerifierDeps;
}  // namespace verifier

// VDEX files contain extracted DEX files. The VdexFile class maps the file to
// memory and provides tools for accessing its individual sections.
//
// In the description below, D is the number of dex files.
//
// File format:
//   VdexFileHeader    fixed-length header
//   VdexSectionHeader[kNumberOfSections]
//
//   Checksum section
//     VdexChecksum[D]
//
//   Optionally:
//      DexSection
//          DEX[0]                array of the input DEX files
//          DEX[1]
//          ...
//          DEX[D-1]
//
//   VerifierDeps
//      4-byte alignment
//      uint32[D]                  DexFileDeps offsets for each dex file
//      DexFileDeps[D][]           verification dependencies
//        4-byte alignment
//        uint32[class_def_size]     TypeAssignability offsets (kNotVerifiedMarker for a class
//                                        that isn't verified)
//        uint32                     Offset of end of AssignabilityType sets
//        uint8[]                    AssignabilityType sets
//        4-byte alignment
//        uint32                     Number of strings
//        uint32[]                   String data offsets for each string
//        uint8[]                    String data


enum VdexSection : uint32_t {
  kChecksumSection = 0,
  kDexFileSection = 1,
  kVerifierDepsSection = 2,
  kTypeLookupTableSection = 3,
  kNumberOfSections = 4,
};

class VdexFile {
 public:
  using VdexChecksum = uint32_t;

  struct VdexSectionHeader {
    VdexSection section_kind;
    uint32_t section_offset;
    uint32_t section_size;

    VdexSectionHeader(VdexSection kind, uint32_t offset, uint32_t size)
        : section_kind(kind), section_offset(offset), section_size(size) {}

    VdexSectionHeader() {}
  };

  struct VdexFileHeader {
   public:
    explicit VdexFileHeader(bool has_dex_section);

    const char* GetMagic() const { return reinterpret_cast<const char*>(magic_); }
    const char* GetVdexVersion() const {
      return reinterpret_cast<const char*>(vdex_version_);
    }
    uint32_t GetNumberOfSections() const {
      return number_of_sections_;
    }
    bool IsMagicValid() const;
    bool IsVdexVersionValid() const;
    bool IsValid() const {
      return IsMagicValid() && IsVdexVersionValid();
    }

    static constexpr uint8_t kVdexInvalidMagic[] = { 'w', 'd', 'e', 'x' };

   private:
    static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' };

    // The format version of the verifier deps header and the verifier deps.
    // Last update: Introduce vdex sections.
    static constexpr uint8_t kVdexVersion[] = { '0', '2', '7', '\0' };

    uint8_t magic_[4];
    uint8_t vdex_version_[4];
    uint32_t number_of_sections_;
  };

  const VdexSectionHeader& GetSectionHeaderAt(uint32_t index) const {
    DCHECK_LT(index, GetVdexFileHeader().GetNumberOfSections());
    return *reinterpret_cast<const VdexSectionHeader*>(
        Begin() + sizeof(VdexFileHeader) + index * sizeof(VdexSectionHeader));
  }

  const VdexSectionHeader& GetSectionHeader(VdexSection kind) const {
    return GetSectionHeaderAt(static_cast<uint32_t>(kind));
  }

  static size_t GetChecksumsOffset() {
    return sizeof(VdexFileHeader) +
        static_cast<size_t>(VdexSection::kNumberOfSections) * sizeof(VdexSectionHeader);
  }

  size_t GetComputedFileSize() const {
    const VdexFileHeader& header = GetVdexFileHeader();
    uint32_t size = sizeof(VdexFileHeader) +
        header.GetNumberOfSections() * sizeof(VdexSectionHeader);
    for (uint32_t i = 0; i < header.GetNumberOfSections(); ++i) {
      size = std::max(size,
                      GetSectionHeaderAt(i).section_offset + GetSectionHeaderAt(i).section_size);
    }
    return size;
  }

  bool HasDexSection() const {
    return GetSectionHeader(VdexSection::kDexFileSection).section_size != 0u;
  }
  uint32_t GetVerifierDepsSize() const {
    return GetSectionHeader(VdexSection::kVerifierDepsSection).section_size;
  }
  uint32_t GetNumberOfDexFiles() const {
    return GetSectionHeader(VdexSection::kChecksumSection).section_size / sizeof(VdexChecksum);
  }

  bool HasTypeLookupTableSection() const {
    return GetVdexFileHeader().GetNumberOfSections() >= (kTypeLookupTableSection + 1);
  }

  const VdexChecksum* GetDexChecksumsArray() const {
    return reinterpret_cast<const VdexChecksum*>(
        Begin() + GetSectionHeader(VdexSection::kChecksumSection).section_offset);
  }

  VdexChecksum GetDexChecksumAt(size_t idx) const {
    DCHECK_LT(idx, GetNumberOfDexFiles());
    return GetDexChecksumsArray()[idx];
  }

  // Note: The file is called "primary" to match the naming with profiles.
  static const constexpr char* kVdexNameInDmFile = "primary.vdex";

  explicit VdexFile(MemMap&& mmap) : mmap_(std::move(mmap)) {}

  // Returns nullptr if the vdex file cannot be opened or is not valid.
  // The mmap_* parameters can be left empty (nullptr/0/false) to allocate at random address.
  static std::unique_ptr<VdexFile> OpenAtAddress(uint8_t* mmap_addr,
                                                 size_t mmap_size,
                                                 bool mmap_reuse,
                                                 const std::string& vdex_filename,
                                                 bool writable,
                                                 bool low_4gb,
                                                 std::string* error_msg);

  // Returns nullptr if the vdex file cannot be opened or is not valid.
  // The mmap_* parameters can be left empty (nullptr/0/false) to allocate at random address.
  static std::unique_ptr<VdexFile> OpenAtAddress(uint8_t* mmap_addr,
                                                 size_t mmap_size,
                                                 bool mmap_reuse,
                                                 int file_fd,
                                                 size_t vdex_length,
                                                 const std::string& vdex_filename,
                                                 bool writable,
                                                 bool low_4gb,
                                                 std::string* error_msg);

  // Returns nullptr if the vdex file cannot be opened or is not valid.
  static std::unique_ptr<VdexFile> Open(const std::string& vdex_filename,
                                        bool writable,
                                        bool low_4gb,
                                        std::string* error_msg) {
    return OpenAtAddress(nullptr,
                         0,
                         false,
                         vdex_filename,
                         writable,
                         low_4gb,
                         error_msg);
  }

  // Returns nullptr if the vdex file cannot be opened or is not valid.
  static std::unique_ptr<VdexFile> Open(int file_fd,
                                        size_t vdex_length,
                                        const std::string& vdex_filename,
                                        bool writable,
                                        bool low_4gb,
                                        std::string* error_msg) {
    return OpenAtAddress(nullptr,
                         0,
                         false,
                         file_fd,
                         vdex_length,
                         vdex_filename,
                         writable,
                         low_4gb,
                         error_msg);
  }

  static std::unique_ptr<VdexFile> OpenFromDm(const std::string& filename,
                                              const ZipArchive& archive);

  const uint8_t* Begin() const { return mmap_.Begin(); }
  const uint8_t* End() const { return mmap_.End(); }
  size_t Size() const { return mmap_.Size(); }
  bool Contains(const uint8_t* pointer) const {
    return pointer >= Begin() && pointer < End();
  }

  const VdexFileHeader& GetVdexFileHeader() const {
    return *reinterpret_cast<const VdexFileHeader*>(Begin());
  }

  ArrayRef<const uint8_t> GetVerifierDepsData() const {
    return ArrayRef<const uint8_t>(
        Begin() + GetSectionHeader(VdexSection::kVerifierDepsSection).section_offset,
        GetSectionHeader(VdexSection::kVerifierDepsSection).section_size);
  }

  bool IsValid() const {
    return mmap_.Size() >= sizeof(VdexFileHeader) && GetVdexFileHeader().IsValid();
  }

  // This method is for iterating over the dex files in the vdex. If `cursor` is null,
  // the first dex file is returned. If `cursor` is not null, it must point to a dex
  // file and this method returns the next dex file if there is one, or null if there
  // is none.
  const uint8_t* GetNextDexFileData(const uint8_t* cursor, uint32_t dex_file_index) const;

  const uint8_t* GetNextTypeLookupTableData(const uint8_t* cursor, uint32_t dex_file_index) const;

  // Get the location checksum of the dex file number `dex_file_index`.
  uint32_t GetLocationChecksum(uint32_t dex_file_index) const {
    DCHECK_LT(dex_file_index, GetNumberOfDexFiles());
    return GetDexChecksumAt(dex_file_index);
  }

  // Open all the dex files contained in this vdex file.
  bool OpenAllDexFiles(std::vector<std::unique_ptr<const DexFile>>* dex_files,
                       std::string* error_msg) const;

  // Writes a vdex into `path` and returns true on success.
  // The vdex will not contain a dex section but will store checksums of `dex_files`,
  // encoded `verifier_deps`, as well as the current boot class path cheksum and
  // encoded `class_loader_context`.
  static bool WriteToDisk(const std::string& path,
                          const std::vector<const DexFile*>& dex_files,
                          const verifier::VerifierDeps& verifier_deps,
                          std::string* error_msg);

  // Returns true if the dex file checksums stored in the vdex header match
  // the checksums in `dex_headers`. Both the number of dex files and their
  // order must match too.
  bool MatchesDexFileChecksums(const std::vector<const DexFile::Header*>& dex_headers) const;

  ClassStatus ComputeClassStatus(Thread* self, Handle<mirror::Class> cls) const
      REQUIRES_SHARED(Locks::mutator_lock_);

  // Return the name of the underlying `MemMap` of the vdex file, typically the
  // location on disk of the vdex file.
  const std::string& GetName() const {
    return mmap_.GetName();
  }

 private:
  bool ContainsDexFile(const DexFile& dex_file) const;

  const uint8_t* DexBegin() const {
    DCHECK(HasDexSection());
    return Begin() + GetSectionHeader(VdexSection::kDexFileSection).section_offset;
  }

  const uint8_t* TypeLookupTableDataBegin() const {
    DCHECK(HasTypeLookupTableSection());
    return Begin() + GetSectionHeader(VdexSection::kTypeLookupTableSection).section_offset;
  }

  MemMap mmap_;

  DISALLOW_COPY_AND_ASSIGN(VdexFile);
};

}  // namespace art

#endif  // ART_RUNTIME_VDEX_FILE_H_
