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

#define LOG_TAG "gatekeeper_hidl_hal_test"

#include <algorithm>
#include <cmath>
#include <string>
#include <vector>

#include <inttypes.h>
#include <unistd.h>

#include <gtest/gtest.h>
#include <hardware/hw_auth_token.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>

#include <android/log.h>
#include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
#include <android/hardware/gatekeeper/1.0/types.h>

#include <log/log.h>

using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::gatekeeper::V1_0::IGatekeeper;
using ::android::hardware::gatekeeper::V1_0::GatekeeperResponse;
using ::android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;

struct GatekeeperRequest {
  uint32_t uid;
  uint64_t challenge;
  hidl_vec<uint8_t> curPwdHandle;
  hidl_vec<uint8_t> curPwd;
  hidl_vec<uint8_t> newPwd;
  GatekeeperRequest() : uid(0), challenge(0) {}
};

// ASSERT_* macros generate return "void" internally
// we have to use EXPECT_* if we return anything but "void"
static const hw_auth_token_t *toAuthToken(GatekeeperResponse &rsp) {
  const hw_auth_token_t *auth_token =
      reinterpret_cast<hw_auth_token_t *>(rsp.data.data());
  const size_t auth_token_size = rsp.data.size();

  EXPECT_NE(nullptr, auth_token);
  EXPECT_EQ(sizeof(hw_auth_token_t), auth_token_size);

  if (auth_token != nullptr && auth_token_size >= sizeof(*auth_token)) {
    // these are in network order: translate to host
    uint32_t auth_type = ntohl(auth_token->authenticator_type);
    uint64_t auth_tstamp = ntohq(auth_token->timestamp);

    EXPECT_EQ(HW_AUTH_PASSWORD, auth_type);
    EXPECT_NE(UINT64_C(~0), auth_tstamp);
    EXPECT_EQ(HW_AUTH_TOKEN_VERSION, auth_token->version);
    //        EXPECT_NE(UINT64_C(0), auth_token->authenticator_id);
    ALOGI("Authenticator ID: %016" PRIX64, auth_token->authenticator_id);
    EXPECT_NE(UINT32_C(0), auth_token->user_id);
  }
  return auth_token;
}

// The main test class for Gatekeeper HIDL HAL.
class GatekeeperHidlTest : public ::testing::TestWithParam<std::string> {
 protected:
  void setUid(uint32_t uid) { uid_ = uid; }

  void doEnroll(GatekeeperRequest &req, GatekeeperResponse &rsp) {
    while (true) {
      auto ret = gatekeeper_->enroll(
          uid_, req.curPwdHandle, req.curPwd, req.newPwd,
          [&rsp](const GatekeeperResponse &cbRsp) { rsp = cbRsp; });
      ASSERT_TRUE(ret.isOk());
      if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
      ALOGI("%s: got retry code; retrying in 1 sec", __func__);
      sleep(1);
    }
  }

  void doVerify(GatekeeperRequest &req, GatekeeperResponse &rsp) {
    while (true) {
      auto ret = gatekeeper_->verify(
          uid_, req.challenge, req.curPwdHandle, req.newPwd,
          [&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
      ASSERT_TRUE(ret.isOk());
      if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
      ALOGI("%s: got retry code; retrying in 1 sec", __func__);
      sleep(1);
    }
  }

  void doDeleteUser(GatekeeperResponse &rsp) {
    while (true) {
      auto ret = gatekeeper_->deleteUser(
          uid_, [&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
      ASSERT_TRUE(ret.isOk());
      if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
      ALOGI("%s: got retry code; retrying in 1 sec", __func__);
      sleep(1);
    }
  }

  void doDeleteAllUsers(GatekeeperResponse &rsp) {
    while (true) {
      auto ret = gatekeeper_->deleteAllUsers(
          [&rsp](const GatekeeperResponse &cb_rsp) { rsp = cb_rsp; });
      ASSERT_TRUE(ret.isOk());
      if (rsp.code != GatekeeperStatusCode::ERROR_RETRY_TIMEOUT) break;
      ALOGI("%s: got retry code; retrying in 1 sec", __func__);
      sleep(1);
    }
  }

  void generatePassword(hidl_vec<uint8_t> &password, uint8_t seed) {
    password.resize(16);
    memset(password.data(), seed, password.size());
  }

  void checkEnroll(GatekeeperResponse &rsp, bool expectSuccess) {
    if (expectSuccess) {
      EXPECT_EQ(GatekeeperStatusCode::STATUS_OK, rsp.code);
      EXPECT_NE(nullptr, rsp.data.data());
      EXPECT_GT(rsp.data.size(), UINT32_C(0));
    } else {
      EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, rsp.code);
      EXPECT_EQ(UINT32_C(0), rsp.data.size());
    }
  }

  void checkVerify(GatekeeperResponse &rsp, uint64_t challenge,
                   bool expectSuccess) {
    if (expectSuccess) {
      EXPECT_GE(rsp.code, GatekeeperStatusCode::STATUS_OK);
      EXPECT_LE(rsp.code, GatekeeperStatusCode::STATUS_REENROLL);

      const hw_auth_token_t *auth_token = toAuthToken(rsp);
      ASSERT_NE(nullptr, auth_token);
      EXPECT_EQ(challenge, auth_token->challenge);
    } else {
      EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, rsp.code);
      EXPECT_EQ(UINT32_C(0), rsp.data.size());
    }
  }

  void enrollNewPassword(hidl_vec<uint8_t> &password, GatekeeperResponse &rsp,
                         bool expectSuccess) {
    GatekeeperRequest req;
    req.newPwd.setToExternal(password.data(), password.size());
    doEnroll(req, rsp);
    checkEnroll(rsp, expectSuccess);
  }

  void verifyPassword(hidl_vec<uint8_t> &password,
                      hidl_vec<uint8_t> &passwordHandle, uint64_t challenge,
                      GatekeeperResponse &verifyRsp, bool expectSuccess) {
    GatekeeperRequest verifyReq;

    // build verify request for the same password (we want it to succeed)
    verifyReq.newPwd = password;
    // use enrolled password handle we've got
    verifyReq.curPwdHandle = passwordHandle;
    verifyReq.challenge = challenge;
    doVerify(verifyReq, verifyRsp);
    checkVerify(verifyRsp, challenge, expectSuccess);
  }

 protected:
  sp<IGatekeeper> gatekeeper_;
  uint32_t uid_;

 public:
  GatekeeperHidlTest() : uid_(0) {}
  virtual void SetUp() override {
    GatekeeperResponse rsp;
    gatekeeper_ = IGatekeeper::getService(GetParam());
    ASSERT_NE(nullptr, gatekeeper_.get());
    doDeleteAllUsers(rsp);
  }

  virtual void TearDown() override {
    GatekeeperResponse rsp;
    doDeleteAllUsers(rsp);
  }
};

/**
 * Ensure we can enroll new password
 */
TEST_P(GatekeeperHidlTest, EnrollSuccess) {
  hidl_vec<uint8_t> password;
  GatekeeperResponse rsp;
  ALOGI("Testing Enroll (expected success)");
  generatePassword(password, 0);
  enrollNewPassword(password, rsp, true);
  ALOGI("Testing Enroll done");
}

/**
 * Ensure we can not enroll empty password
 */
TEST_P(GatekeeperHidlTest, EnrollNoPassword) {
  hidl_vec<uint8_t> password;
  GatekeeperResponse rsp;
  ALOGI("Testing Enroll (expected failure)");
  enrollNewPassword(password, rsp, false);
  ALOGI("Testing Enroll done");
}

/**
 * Ensure we can successfully verify previously enrolled password
 */
TEST_P(GatekeeperHidlTest, VerifySuccess) {
  GatekeeperResponse enrollRsp;
  GatekeeperResponse verifyRsp;
  hidl_vec<uint8_t> password;

  ALOGI("Testing Enroll+Verify (expected success)");
  generatePassword(password, 0);
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);

  ALOGI("Testing unenrolled password doesn't verify");
  generatePassword(password, 1);
  verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);
  ALOGI("Testing Enroll+Verify done");
}

/**
 * Ensure that passwords containing a NUL byte aren't truncated
 */
TEST_P(GatekeeperHidlTest, PasswordIsBinaryData) {
  GatekeeperResponse enrollRsp;
  GatekeeperResponse verifyRsp;
  hidl_vec<uint8_t> rightPassword = {'A', 'B', 'C', '\0', 'D', 'E', 'F'};
  hidl_vec<uint8_t> wrongPassword = {'A', 'B', 'C', '\0', '\0', '\0', '\0'};

  ALOGI("Testing Enroll+Verify of password with embedded NUL (expected success)");
  enrollNewPassword(rightPassword, enrollRsp, true);
  verifyPassword(rightPassword, enrollRsp.data, 1, verifyRsp, true);

  ALOGI("Testing Verify of wrong password (expected failure)");
  verifyPassword(wrongPassword, enrollRsp.data, 1, verifyRsp, false);

  ALOGI("PasswordIsBinaryData test done");
}

/**
 * Ensure that long passwords aren't truncated
 */
TEST_P(GatekeeperHidlTest, LongPassword) {
  GatekeeperResponse enrollRsp;
  GatekeeperResponse verifyRsp;
  hidl_vec<uint8_t> password;

  password.resize(64); // maximum length used by Android
  memset(password.data(), 'A', password.size());

  ALOGI("Testing Enroll+Verify of long password (expected success)");
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 1, verifyRsp, true);

  ALOGI("Testing Verify of wrong password (expected failure)");
  password[password.size() - 1] ^= 1;
  verifyPassword(password, enrollRsp.data, 1, verifyRsp, false);

  ALOGI("LongPassword test done");
}

/**
 * Ensure we can securely update password (keep the same
 * secure user_id) if we prove we know old password
 */
TEST_P(GatekeeperHidlTest, TrustedReenroll) {
  GatekeeperResponse enrollRsp;
  GatekeeperRequest reenrollReq;
  GatekeeperResponse reenrollRsp;
  GatekeeperResponse verifyRsp;
  GatekeeperResponse reenrollVerifyRsp;
  hidl_vec<uint8_t> password;
  hidl_vec<uint8_t> newPassword;

  generatePassword(password, 0);

  ALOGI("Testing Trusted Reenroll (expected success)");
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
  ALOGI("Primary Enroll+Verify done");

  generatePassword(newPassword, 1);
  reenrollReq.newPwd.setToExternal(newPassword.data(), newPassword.size());
  reenrollReq.curPwd.setToExternal(password.data(), password.size());
  reenrollReq.curPwdHandle.setToExternal(enrollRsp.data.data(),
                                         enrollRsp.data.size());

  doEnroll(reenrollReq, reenrollRsp);
  checkEnroll(reenrollRsp, true);
  verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
  ALOGI("Trusted ReEnroll+Verify done");

  const hw_auth_token_t *first = toAuthToken(verifyRsp);
  const hw_auth_token_t *second = toAuthToken(reenrollVerifyRsp);
  if (first != nullptr && second != nullptr) {
    EXPECT_EQ(first->user_id, second->user_id);
  }
  ALOGI("Testing Trusted Reenroll done");
}

/**
 * Ensure we can update password (and get new
 * secure user_id) if we don't know old password
 */
TEST_P(GatekeeperHidlTest, UntrustedReenroll) {
  GatekeeperResponse enrollRsp;
  GatekeeperResponse reenrollRsp;
  GatekeeperResponse verifyRsp;
  GatekeeperResponse reenrollVerifyRsp;
  hidl_vec<uint8_t> password;
  hidl_vec<uint8_t> newPassword;

  ALOGI("Testing Untrusted Reenroll (expected success)");
  generatePassword(password, 0);
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
  ALOGI("Primary Enroll+Verify done");

  generatePassword(newPassword, 1);
  enrollNewPassword(newPassword, reenrollRsp, true);
  verifyPassword(newPassword, reenrollRsp.data, 0, reenrollVerifyRsp, true);
  ALOGI("Untrusted ReEnroll+Verify done");

  const hw_auth_token_t *first = toAuthToken(verifyRsp);
  const hw_auth_token_t *second = toAuthToken(reenrollVerifyRsp);
  if (first != nullptr && second != nullptr) {
    EXPECT_NE(first->user_id, second->user_id);
  }
  ALOGI("Testing Untrusted Reenroll done");
}

/**
 * Ensure we dont get successful verify with invalid data
 */
TEST_P(GatekeeperHidlTest, VerifyNoData) {
  hidl_vec<uint8_t> password;
  hidl_vec<uint8_t> passwordHandle;
  GatekeeperResponse verifyRsp;

  ALOGI("Testing Verify (expected failure)");
  verifyPassword(password, passwordHandle, 0, verifyRsp, false);
  EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, verifyRsp.code);
  ALOGI("Testing Verify done");
}

/**
 * Ensure we can not verify password after we enrolled it and then deleted user
 */
TEST_P(GatekeeperHidlTest, DeleteUserTest) {
  hidl_vec<uint8_t> password;
  GatekeeperResponse enrollRsp;
  GatekeeperResponse verifyRsp;
  GatekeeperResponse delRsp;
  ALOGI("Testing deleteUser (expected success)");
  setUid(10001);
  generatePassword(password, 0);
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
  ALOGI("Enroll+Verify done");
  doDeleteUser(delRsp);
  EXPECT_EQ(UINT32_C(0), delRsp.data.size());
  EXPECT_TRUE(delRsp.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
              delRsp.code == GatekeeperStatusCode::STATUS_OK);
  ALOGI("DeleteUser done");
  if (delRsp.code == GatekeeperStatusCode::STATUS_OK) {
    verifyPassword(password, enrollRsp.data, 0, verifyRsp, false);
    EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE, verifyRsp.code);
    ALOGI("Verify after Delete done (must fail)");
  }
  ALOGI("Testing deleteUser done: rsp=%" PRIi32, delRsp.code);
}

/**
 * Ensure we can not delete a user that does not exist
 */
TEST_P(GatekeeperHidlTest, DeleteInvalidUserTest) {
  hidl_vec<uint8_t> password;
  GatekeeperResponse enrollRsp;
  GatekeeperResponse verifyRsp;
  GatekeeperResponse delRsp1;
  GatekeeperResponse delRsp2;
  ALOGI("Testing deleteUser (expected failure)");
  setUid(10002);
  generatePassword(password, 0);
  enrollNewPassword(password, enrollRsp, true);
  verifyPassword(password, enrollRsp.data, 0, verifyRsp, true);
  ALOGI("Enroll+Verify done");

  // Delete the user
  doDeleteUser(delRsp1);
  EXPECT_EQ(UINT32_C(0), delRsp1.data.size());
  EXPECT_TRUE(delRsp1.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
              delRsp1.code == GatekeeperStatusCode::STATUS_OK);

  // Delete the user again
  doDeleteUser(delRsp2);
  EXPECT_EQ(UINT32_C(0), delRsp2.data.size());
  EXPECT_TRUE(delRsp2.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
              delRsp2.code == GatekeeperStatusCode::ERROR_GENERAL_FAILURE);
  ALOGI("DeleteUser done");
  ALOGI("Testing deleteUser done: rsp=%" PRIi32, delRsp2.code);
}

/**
 * Ensure we can not verify passwords after we enrolled them and then deleted
 * all users
 */
TEST_P(GatekeeperHidlTest, DeleteAllUsersTest) {
  struct UserData {
    uint32_t userId;
    hidl_vec<uint8_t> password;
    GatekeeperResponse enrollRsp;
    GatekeeperResponse verifyRsp;
    UserData(int id) { userId = id; }
  } users[3]{10001, 10002, 10003};
  GatekeeperResponse delAllRsp;
  ALOGI("Testing deleteAllUsers (expected success)");

  // enroll multiple users
  for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
    setUid(users[i].userId);
    generatePassword(users[i].password, (i % 255) + 1);
    enrollNewPassword(users[i].password, users[i].enrollRsp, true);
  }
  ALOGI("Multiple users enrolled");

  // verify multiple users
  for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
    setUid(users[i].userId);
    verifyPassword(users[i].password, users[i].enrollRsp.data, 0,
                   users[i].verifyRsp, true);
  }
  ALOGI("Multiple users verified");

  doDeleteAllUsers(delAllRsp);
  EXPECT_EQ(UINT32_C(0), delAllRsp.data.size());
  EXPECT_TRUE(delAllRsp.code == GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED ||
              delAllRsp.code == GatekeeperStatusCode::STATUS_OK);
  ALOGI("All users deleted");

  if (delAllRsp.code == GatekeeperStatusCode::STATUS_OK) {
    // verify multiple users after they are deleted; all must fail
    for (size_t i = 0; i < sizeof(users) / sizeof(users[0]); ++i) {
      setUid(users[i].userId);
      verifyPassword(users[i].password, users[i].enrollRsp.data, 0,
                     users[i].verifyRsp, false);
      EXPECT_EQ(GatekeeperStatusCode::ERROR_GENERAL_FAILURE,
                users[i].verifyRsp.code);
    }
    ALOGI("Multiple users verified after delete (all must fail)");
  }

  ALOGI("Testing deleteAllUsers done: rsp=%" PRIi32, delAllRsp.code);
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GatekeeperHidlTest);
INSTANTIATE_TEST_SUITE_P(
        PerInstance, GatekeeperHidlTest,
        testing::ValuesIn(android::hardware::getAllHalInstanceNames(IGatekeeper::descriptor)),
        android::hardware::PrintInstanceNameToString);
