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

#include "utils.h"

#include <android-base/logging.h>
#include <android-base/properties.h>

#include <map>
#include <set>
#include <string>
#include <vector>

using android::base::GetUintProperty;

namespace android {
namespace vintf {
namespace testing {

// Path to directory on target containing test data.
const string kDataDir = "/data/local/tmp/";

// Name of file containing HAL hashes.
const string kHashFileName = "current.txt";

// Map from package name to package root.
const map<string, string> kPackageRoot = {
    {"android.frameworks", "frameworks/hardware/interfaces/"},
    {"android.hardware", "hardware/interfaces/"},
    {"android.hidl", "system/libhidl/transport/"},
    {"android.system", "system/hardware/interfaces/"},
};

// HALs that are allowed to be passthrough under Treble rules.
const set<string> kPassthroughHals = {
    "android.hardware.graphics.mapper", "android.hardware.renderscript",
    "android.hidl.memory",
};

std::string SanitizeTestCaseName(std::string original) {
  for (char &c : original) {
    if (!isalnum(c)) {
      c = '_';
    }
  }
  return original;
}

HidlInstance::HidlInstance(const ManifestInstance &other)
    : ManifestInstance(other) {
  CHECK_EQ(format(), HalFormat::HIDL);
}

ostream &operator<<(ostream &os, const HidlInstance& val) {
  return os << val.transport() << " HAL " << val.fq_name().string() << "/"
            << val.instance_name();
}

string HidlInstance::test_case_name() const {
  return SanitizeTestCaseName(fq_name().string() + "/" + instance_name());
}

AidlInstance::AidlInstance(const ManifestInstance &other)
    : ManifestInstance(other) {
  CHECK_EQ(format(), HalFormat::AIDL);
}

string AidlInstance::test_case_name() const {
  return SanitizeTestCaseName(
      toAidlFqnameString(package(), interface(), instance()) + "_V" +
      to_string(version()));
}

ostream &operator<<(ostream &os, const AidlInstance& val) {
  os << toAidlFqnameString(val.package(), val.interface(), val.instance())
     << ", Version " << val.version();
  if (val.updatable_via_apex()) {
    os << ", updatable_via_apex = " << *val.updatable_via_apex();
  }
  return os;
}

NativeInstance::NativeInstance(const ManifestInstance &other)
    : ManifestInstance(other) {
  CHECK_EQ(format(), HalFormat::NATIVE);
}

string NativeInstance::test_case_name() const {
  return SanitizeTestCaseName(
      toAidlFqnameString(package(), interface(), instance()) + "_V" +
      to_string(version()));
}

ostream &operator<<(ostream &os, const NativeInstance &val) {
  os << "Native HAL { package: " << val.package()
     << " version: " << val.major_version() << "." << val.minor_version()
     << " interface: " << val.interface() << " instance: " << val.instance()
     << " }";
  return os;
}

uint64_t GetVendorApiLevel() {
  return GetUintProperty<uint64_t>("ro.vendor.api_level", 0);
}

// For a given interface returns package root if known. Returns empty string
// otherwise.
const string PackageRoot(const FQName &fq_iface_name) {
  for (const auto &package_root : kPackageRoot) {
    if (fq_iface_name.inPackage(package_root.first)) {
      return package_root.second;
    }
  }
  return "";
}

// Returns true iff HAL interface is Android platform.
bool IsAndroidPlatformInterface(const FQName &fq_iface_name) {
  // Package roots are only known for Android platform packages.
  return !PackageRoot(fq_iface_name).empty();
}

// Returns the set of released hashes for a given HAL interface.
set<string> ReleasedHashes(const FQName &fq_iface_name) {
  set<string> released_hashes{};
  string err = "";

  string file_path = kDataDir + PackageRoot(fq_iface_name) + kHashFileName;
  auto hashes = Hash::lookupHash(file_path, fq_iface_name.string(), &err);
  released_hashes.insert(hashes.begin(), hashes.end());
  return released_hashes;
}

// Returns the partition that a HAL is associated with.
Partition PartitionOfProcess(int32_t pid) {
  auto partition = android::procpartition::getPartition(pid);

  // TODO(b/70033981): remove once ODM and Vendor manifests are distinguished
  if (partition == Partition::ODM) {
    partition = Partition::VENDOR;
  }

  return partition;
}

Partition PartitionOfType(SchemaType type) {
  switch (type) {
    case SchemaType::DEVICE:
      return Partition::VENDOR;
    case SchemaType::FRAMEWORK:
      return Partition::SYSTEM;
  }
  return Partition::UNKNOWN;
}

}  // namespace testing
}  // namespace vintf
}  // namespace android

namespace std {
void PrintTo(const android::vintf::testing::HalManifestPtr &v, ostream *os) {
  if (v == nullptr) {
    *os << "nullptr";
    return;
  }
  *os << to_string(v->type()) << " manifest";
}
}  // namespace std
