/*
 * 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.
 */

#define ATRACE_TAG ATRACE_TAG_RESOURCES

#include "androidfw/Idmap.h"

#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"

#ifdef _WIN32
#ifdef ERROR
#undef ERROR
#endif
#endif

using ::android::base::StringPrintf;

namespace android {

// See frameworks/base/cmds/idmap2/include/idmap2/Idmap.h for full idmap file format specification.
struct Idmap_header {
  // Always 0x504D4449 ('IDMP')
  uint32_t magic;
  uint32_t version;

  uint32_t target_crc32;
  uint32_t overlay_crc32;

  uint32_t fulfilled_policies;
  uint32_t enforce_overlayable;

  // overlay_path, target_path, and other string values encoded in the idmap header and read and
  // stored in separate structures. This allows the idmap header data to be casted to this struct
  // without having to read/store each header entry separately.
};

struct Idmap_data_header {
  uint32_t target_entry_count;
  uint32_t target_inline_entry_count;
  uint32_t target_inline_entry_value_count;
  uint32_t configuration_count;
  uint32_t overlay_entry_count;

  uint32_t string_pool_index_offset;
};

struct Idmap_target_entry_inline {
  uint32_t start_value_index;
  uint32_t value_count;
};

struct Idmap_target_entry_inline_value {
  uint32_t config_index;
  Res_value value;
};

static constexpr uint32_t convert_dev_target_id(uint32_t dev_target_id) {
  return (0x00FFFFFFU & dtohl(dev_target_id));
}

OverlayStringPool::OverlayStringPool(const LoadedIdmap* loaded_idmap)
    : data_header_(loaded_idmap->data_header_),
      idmap_string_pool_(loaded_idmap->string_pool_.get()) { };

OverlayStringPool::~OverlayStringPool() {
  uninit();
}

base::expected<StringPiece16, NullOrIOError> OverlayStringPool::stringAt(size_t idx) const {
  const size_t offset = dtohl(data_header_->string_pool_index_offset);
  if (idmap_string_pool_ != nullptr && idx >= ResStringPool::size() && idx >= offset) {
    return idmap_string_pool_->stringAt(idx - offset);
  }

  return ResStringPool::stringAt(idx);
}

base::expected<StringPiece, NullOrIOError> OverlayStringPool::string8At(size_t idx) const {
  const size_t offset = dtohl(data_header_->string_pool_index_offset);
  if (idmap_string_pool_ != nullptr && idx >= ResStringPool::size() && idx >= offset) {
    return idmap_string_pool_->string8At(idx - offset);
  }

  return ResStringPool::string8At(idx);
}

size_t OverlayStringPool::size() const {
  return ResStringPool::size() + (idmap_string_pool_ != nullptr ? idmap_string_pool_->size() : 0U);
}

OverlayDynamicRefTable::OverlayDynamicRefTable(const Idmap_data_header* data_header,
                                               Idmap_overlay_entries entries,
                                               uint8_t target_assigned_package_id)
    : data_header_(data_header),
      entries_(entries),
      target_assigned_package_id_(target_assigned_package_id) {
}

status_t OverlayDynamicRefTable::lookupResourceId(uint32_t* resId) const {
  const auto count = dtohl(data_header_->overlay_entry_count);
  const auto overlay_it_end = entries_.overlay_id + count;
  const auto entry_it = std::lower_bound(entries_.overlay_id, overlay_it_end, *resId,
                                         [](uint32_t dev_overlay_id, uint32_t overlay_id) {
                                           return dtohl(dev_overlay_id) < overlay_id;
                                         });

  if (entry_it == overlay_it_end || dtohl(*entry_it) != *resId) {
    // A mapping for the target resource id could not be found.
    return DynamicRefTable::lookupResourceId(resId);
  }

  const auto index = entry_it - entries_.overlay_id;
  *resId = convert_dev_target_id(entries_.target_id[index]) |
           (((uint32_t)target_assigned_package_id_) << 24U);
  return NO_ERROR;
}

status_t OverlayDynamicRefTable::lookupResourceIdNoRewrite(uint32_t* resId) const {
  return DynamicRefTable::lookupResourceId(resId);
}

IdmapResMap::IdmapResMap(const Idmap_data_header* data_header, const Idmap_constraints& constraints,
                         Idmap_target_entries entries, Idmap_target_inline_entries inline_entries,
                         const Idmap_target_entry_inline_value* inline_entry_values,
                         const ConfigDescription* configs, uint8_t target_assigned_package_id,
                         const OverlayDynamicRefTable* overlay_ref_table)
    : data_header_(data_header),
      constraints_(constraints),
      entries_(entries),
      inline_entries_(inline_entries),
      inline_entry_values_(inline_entry_values),
      configurations_(configs),
      target_assigned_package_id_(target_assigned_package_id),
      overlay_ref_table_(overlay_ref_table) {
}

IdmapResMap::Result IdmapResMap::Lookup(uint32_t target_res_id) const {
  if ((target_res_id >> 24U) != target_assigned_package_id_) {
    // The resource id must have the same package id as the target package.
    return {};
  }

  // The resource ids encoded within the idmap are build-time resource ids so do not consider the
  // package id when determining if the resource in the target package is overlaid.
  target_res_id &= 0x00FFFFFFU;

  // Check if the target resource is mapped to an overlay resource.
  const auto target_end = entries_.target_id + dtohl(data_header_->target_entry_count);
  auto target_it = std::lower_bound(entries_.target_id, target_end, target_res_id,
                                    [](uint32_t dev_target_id, uint32_t target_id) {
                                      return convert_dev_target_id(dev_target_id) < target_id;
                                    });

  if (target_it != target_end && convert_dev_target_id(*target_it) == target_res_id) {
    const auto index = target_it - entries_.target_id;
    uint32_t overlay_resource_id = dtohl(entries_.overlay_id[index]);
    // Lookup the resource without rewriting the overlay resource id back to the target resource id
    // being looked up.
    overlay_ref_table_->lookupResourceIdNoRewrite(&overlay_resource_id);
    return Result(overlay_resource_id);
  }

  // Check if the target resources is mapped to an inline table entry.
  const auto inline_entry_target_end =
      inline_entries_.target_id + dtohl(data_header_->target_inline_entry_count);
  const auto inline_entry_target_it =
      std::lower_bound(inline_entries_.target_id, inline_entry_target_end, target_res_id,
                       [](uint32_t dev_target_id, uint32_t target_id) {
                         return convert_dev_target_id(dev_target_id) < target_id;
                       });

  if (inline_entry_target_it != inline_entry_target_end &&
      convert_dev_target_id(*inline_entry_target_it) == target_res_id) {
    const auto index = inline_entry_target_it - inline_entries_.target_id;
    std::map<ConfigDescription, Res_value> values_map;
    const auto& inline_entry = inline_entries_.entry[index];
    for (int i = 0; i < dtohl(inline_entry.value_count); i++) {
      const auto& value = inline_entry_values_[dtohl(inline_entry.start_value_index) + i];
      const auto& config = configurations_[dtohl(value.config_index)];
      values_map[config] = value.value;
    }
    return Result(std::move(values_map));
  }
  return {};
}

namespace {
template <typename T>
const T* ReadType(const uint8_t** in_out_data_ptr, size_t* in_out_size, const char* label,
                  size_t count = 1) {
  if (!util::IsFourByteAligned(*in_out_data_ptr)) {
    LOG(ERROR) << "Idmap " << label << " in " << __func__ << " is not word aligned.";
    return {};
  }
  if ((*in_out_size / sizeof(T)) < count) {
    LOG(ERROR) << "Idmap too small for the number of " << label << " in " << __func__
               << " entries (" << count << ").";
    return nullptr;
  }
  auto data_ptr = *in_out_data_ptr;
  const size_t read_size = sizeof(T) * count;
  *in_out_data_ptr += read_size;
  *in_out_size -= read_size;
  return reinterpret_cast<const T*>(data_ptr);
}

std::optional<std::string_view> ReadString(const uint8_t** in_out_data_ptr, size_t* in_out_size,
                                           const char* label) {
  const auto* len = ReadType<uint32_t>(in_out_data_ptr, in_out_size, label);
  if (len == nullptr) {
    return {};
  }
  const auto* data = ReadType<char>(in_out_data_ptr, in_out_size, label, *len);
  if (data == nullptr) {
    return {};
  }
  // Strings are padded to the next 4 byte boundary.
  const uint32_t padding_size = (4U - ((size_t)*in_out_data_ptr & 0x3U)) % 4U;
  for (uint32_t i = 0; i < padding_size; i++) {
    if (**in_out_data_ptr != 0) {
      LOG(ERROR) << " Idmap padding of " << label << " in " << __func__ << " is non-zero.";
      return {};
    }
    *in_out_data_ptr += sizeof(uint8_t);
    *in_out_size -= sizeof(uint8_t);
  }
  return std::string_view(data, *len);
}
}  // namespace

// O_PATH is a lightweight way of creating an FD, only exists on Linux
#ifndef O_PATH
#define O_PATH (0)
#endif

LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* header,
                         const Idmap_data_header* data_header, const Idmap_constraints& constraints,
                         Idmap_target_entries target_entries,
                         Idmap_target_inline_entries target_inline_entries,
                         const Idmap_target_entry_inline_value* inline_entry_values,
                         const ConfigDescription* configs, Idmap_overlay_entries overlay_entries,
                         std::unique_ptr<ResStringPool>&& string_pool,
                         std::string_view overlay_apk_path, std::string_view target_apk_path)
    : header_(header),
      data_header_(data_header),
      constraints_(constraints),
      target_entries_(target_entries),
      target_inline_entries_(target_inline_entries),
      inline_entry_values_(inline_entry_values),
      configurations_(configs),
      overlay_entries_(overlay_entries),
      string_pool_(std::move(string_pool)),
      overlay_apk_path_(overlay_apk_path),
      target_apk_path_(target_apk_path),
      idmap_last_mod_time_(kInvalidModDate) {
  if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
      !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
        isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
    idmap_fd_.reset(
        android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
    idmap_last_mod_time_ = getFileModDate(idmap_fd_);
  }
}

std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
  ATRACE_CALL();
  size_t data_size = idmap_data.size();
  auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());

  // Parse the idmap header
  auto header = ReadType<Idmap_header>(&data_ptr, &data_size, "header");
  if (header == nullptr) {
    return {};
  }
  if (dtohl(header->magic) != kIdmapMagic) {
    LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
                               dtohl(header->magic), kIdmapMagic);
    return {};
  }
  if (dtohl(header->version) != kIdmapCurrentVersion) {
    // We are strict about versions because files with this format are generated at runtime and
    // don't need backwards compatibility.
    LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
                               dtohl(header->version), kIdmapCurrentVersion);
    return {};
  }
  std::optional<std::string_view> target_path = ReadString(&data_ptr, &data_size, "target path");
  if (!target_path) {
    return {};
  }
  std::optional<std::string_view> overlay_path = ReadString(&data_ptr, &data_size, "overlay path");
  if (!overlay_path) {
    return {};
  }
  if (!ReadString(&data_ptr, &data_size, "target name") ||
      !ReadString(&data_ptr, &data_size, "debug info")) {
    return {};
  }

  auto constraint_count = ReadType<uint32_t>(&data_ptr, &data_size, "constraint count");
  if (!constraint_count) {
    LOG(ERROR) << "idmap doesn't have constraint count";
    return {};
  }
  auto constraint_entries = *constraint_count > 0 ?
      ReadType<Idmap_constraint>(&data_ptr, &data_size, "constraints", dtohl(*constraint_count))
      : nullptr;
  if (*constraint_count > 0 && !constraint_entries) {
    LOG(ERROR) << "no constraint entries in idmap with non-zero constraints";
    return {};
  }
  Idmap_constraints constraints{.constraint_count = *constraint_count,
                                .constraint_entries = constraint_entries};

  // Parse the idmap data blocks. Currently idmap2 can only generate one data block.
  auto data_header = ReadType<Idmap_data_header>(&data_ptr, &data_size, "data header");
  if (data_header == nullptr) {
    return {};
  }
  Idmap_target_entries target_entries{
      .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.target_id",
                                      dtohl(data_header->target_entry_count)),
      .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "entries.overlay_id",
                                       dtohl(data_header->target_entry_count)),
  };
  if (!target_entries.target_id || !target_entries.overlay_id) {
    return {};
  }
  Idmap_target_inline_entries target_inline_entries{
      .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "target inline.target_id",
                                      dtohl(data_header->target_inline_entry_count)),
      .entry = ReadType<Idmap_target_entry_inline>(&data_ptr, &data_size, "target inline.entry",
                                                   dtohl(data_header->target_inline_entry_count))};
  if (!target_inline_entries.target_id || !target_inline_entries.entry) {
    return {};
  }

  auto target_inline_entry_values = ReadType<Idmap_target_entry_inline_value>(
      &data_ptr, &data_size, "target inline values",
      dtohl(data_header->target_inline_entry_value_count));
  if (target_inline_entry_values == nullptr) {
    return {};
  }

  auto configurations = ReadType<ConfigDescription>(
      &data_ptr, &data_size, "configurations",
      dtohl(data_header->configuration_count));
  if (configurations == nullptr) {
    return {};
  }

  Idmap_overlay_entries overlay_entries{
      .overlay_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.overlay_id",
                                       dtohl(data_header->overlay_entry_count)),
      .target_id = ReadType<uint32_t>(&data_ptr, &data_size, "overlay entries.target_id",
                                      dtohl(data_header->overlay_entry_count)),
  };
  if (!overlay_entries.overlay_id || !overlay_entries.target_id) {
    return {};
  }
  std::optional<std::string_view> string_pool = ReadString(&data_ptr, &data_size, "string pool");
  if (!string_pool) {
    return {};
  }
  auto idmap_string_pool = util::make_unique<ResStringPool>();
  if (!string_pool->empty()) {
    const status_t err = idmap_string_pool->setTo(string_pool->data(), string_pool->size());
    if (err != NO_ERROR) {
      LOG(ERROR) << "idmap string pool corrupt.";
      return {};
    }
  }

  if (data_size != 0) {
    LOG(ERROR) << "idmap parsed with " << data_size << "bytes remaining";
    return {};
  }

  // Can't use make_unique because LoadedIdmap constructor is private.
  return std::unique_ptr<LoadedIdmap>(
      new LoadedIdmap(std::string(idmap_path), header, data_header, constraints, target_entries,
                      target_inline_entries, target_inline_entry_values, configurations,
                      overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}

UpToDate LoadedIdmap::IsUpToDate() const {
  if (idmap_last_mod_time_ == kInvalidModDate) {
    return UpToDate::Always;
  }
  return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
}

}  // namespace android
