#include <gtest/gtest.h>

#include <stdint.h>
#include <fstream>

#include "adb.h"
#include "signing.h"

const std::string kCanaryHostPath = "./functional/out/android/canary_client/canary";
const std::string kCanaryDevicePath = "/vendor/bin/canary";
const std::string kAuthenticateAndroidTestResult = "test_result.bin";

const std::string kApkPath = "/data/app/*/com.samsung.androidpaclient*/base.apk";
const std::string kOdexPath = "/data/app/*/com.samsung.androidpaclient*/oat/arm*/base.odex";
const std::string kVdexPath = "/data/app/*/com.samsung.androidpaclient*/oat/arm*/base.vdex";

const std::vector<std::string> kAndroidAppPathes {kApkPath, kOdexPath, kVdexPath};

class AuthenticateHostTest : public ::testing::Test {
 protected:

  virtual void SetUp() {
    // Guard pause before any test cases
    sleep(1);
  }
  virtual void TearDown() {
    Signing::FivePush(kCanaryHostPath);
    Adb::Shell("rm -rf /system/bin/test_folder");
    Adb::Shell("rm -f /system/bin/canary_symlink");
    Adb::Shell("rm -rf /vendor/bin/test_folder");
    Adb::Shell("rm -f /vendor/bin/canary_symlink");
  }
};

class AuthenticateTestAndroid : public ::testing::Test {
public:
  static void TearDownTestCase() {
    system("make -f android_pa_client.mk install > /dev/null");
  }

  void SetUp() {
    system("make -f android_pa_client.mk install > /dev/null");
    Adb::Shell("cmd package compile -f -m speed com.samsung.androidpaclient");
    std::cout << "Running Android Pa Client ..." << std::endl;
    Adb::Shell("am force-stop com.samsung.androidpaclient");
  }

  void TearDown() {
    std::cout << "Stopping Android Pa Client ..." << std::endl;
    Adb::Shell("am force-stop com.samsung.androidpaclient");
    Adb::Shell("rm -f /sdcard/PA/test_result.bin");
    system("rm -f test_result.bin");
  }
};

static bool CheckAuthenticationResult(int *res) {
  Adb::Shell("am broadcast -a com.samsung.androidpaclient.TEST "
             "-n com.samsung.androidpaclient/.IntentReceiver");

  sleep(5);
  system("adb pull /sdcard/PA/test_result.bin");

  std::ifstream pa_result_file(kAuthenticateAndroidTestResult.c_str(),
                               std::ifstream::in | std::ifstream::binary);

  bool result = pa_result_file.good();

  pa_result_file.read((char*)res, sizeof(res));
  pa_result_file.close();

  return result;
}

TEST_F(AuthenticateHostTest, 01_AuthenticateWithEng_Passed) {
  int res = Signing::FivePush(kCanaryHostPath);
  EXPECT_EQ(0, res) <<
      "Application may be not pushed on device";

  res = Adb::Shell("canary | grep SUCCESS");
  ASSERT_EQ(0, res) <<
      "Application SHOULD be authenticated if ENG certificate is correct";
}

TEST_F(AuthenticateHostTest, 02_AuthenticateWithIncorrectKeyId_Failed) {
  int res = Signing::FivePush(kCanaryHostPath, true);
  EXPECT_EQ(0, res) <<
     "Application may be not pushed on device";

  res = Adb::Shell("canary | grep SUCCESS");
  ASSERT_NE(0, res) <<
     "Application SHOULD NOT be authenticated if certificate has unrelated key_id and signature";
}

TEST_F(AuthenticateHostTest, DISABLED_03_AuthenticateSymlinkInSystemBin_Passed) {
  int res = Signing::FivePush(kCanaryHostPath);
  EXPECT_EQ(0, res) << "Application may be not pushed on device";

  res = Adb::Shell("ln -s /system/bin/canary /system/bin/canary_symlink");
  ASSERT_EQ(0, res) << "Cannot be created symlink to canary";

  res = Adb::Shell("canary_symlink | grep SUCCESS");
  ASSERT_EQ(0, res) << "Application SHOULD be authenticated";
}

TEST_F(AuthenticateHostTest, DISABLED_04_AuthenticateSymlinkInSystemBinFolder_Passed) {
  int res = Signing::FivePush(kCanaryHostPath);
  EXPECT_EQ(0, res) << "Application may be not pushed on device";

  res = Adb::Shell("mkdir -p /system/bin/test_folder");
  ASSERT_EQ(0, res) << "Cannot be created test_folder in /system/bin/";

  res = Adb::Shell("ln -s /system/bin/canary /system/bin/test_folder/canary_symlink");
  ASSERT_EQ(0, res) << "Cannot be created symlink to canary";

  res = Adb::Shell("/system/bin/test_folder/canary_symlink | grep SUCCESS");
  ASSERT_EQ(0, res) << "Application SHOULD be authenticated";
}

TEST_F(AuthenticateHostTest, 05_AuthenticateSymlinkInVendorBin_Passed) {
  int res = Signing::FivePush(kCanaryHostPath);
  EXPECT_EQ(0, res) << "Application may be not pushed on device";

  res = Adb::Shell("ln -s /vendor/bin/canary /vendor/bin/canary_symlink");
  ASSERT_EQ(0, res) << "Cannot be created symlink to canary";

  res = Adb::Shell("canary_symlink | grep SUCCESS");
  ASSERT_EQ(0, res) << "Application SHOULD be authenticated";
}

TEST_F(AuthenticateHostTest, 06_AuthenticateSymlinkInVendorBinFolder_Passed) {
  int res = Signing::FivePush(kCanaryHostPath);
  EXPECT_EQ(0, res) << "Application may be not pushed on device";

  res = Adb::Shell("mkdir -p /vendor/bin/test_folder");
  ASSERT_EQ(0, res) << "Cannot be created test_folder in /vendor/bin/";

  res = Adb::Shell("ln -s /vendor/bin/canary /vendor/bin/test_folder/canary_symlink");
  ASSERT_EQ(0, res) << "Cannot be created symlink to canary";

  res = Adb::Shell("/vendor/bin/test_folder/canary_symlink | grep SUCCESS");
  ASSERT_EQ(0, res) << "Application SHOULD be authenticated";
}

TEST_F(AuthenticateTestAndroid, 01_AppSignedByHMACAuthentication_Passed) {
  int res;

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_EQ(0, res);
}

TEST_F(AuthenticateTestAndroid, 02_AppAbsentPAIDAuthentication_Failed) {
  // Remove PA ID from each Android Application files
  for (auto &path: kAndroidAppPathes) {
    Adb::Shell("xattr_manager -r --name=user.pa " + path);
  }

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  int res;
  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_NE(0, res);
}

TEST_F(AuthenticateTestAndroid, 03_AppIncorrectPAIDAuthentication_Failed) {
  // Set incorrect PA ID for each Android Application files
  for (auto &path: kAndroidAppPathes) {
    Signing::SetXattr(path, "user.pa", "abcdef");
  }

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  int res;
  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_NE(0, res);
}

TEST_F(AuthenticateTestAndroid, 04_AppSignedAsNativeAuthentication_Failed) {
  // Set native PA ID for each Android Application files
  for (auto &path: kAndroidAppPathes) {
    std::string b64_five_signature;
    std::string b64_pa_signature;

    Signing::GetXattr(path, "security.five", b64_five_signature);
    Signing::PaNativeSignature("base.apk", b64_five_signature, b64_pa_signature);
    Signing::SetXattr(path, "user.pa", b64_pa_signature);
  }

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  int res;
  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_NE(0, res);
}

TEST_F(AuthenticateTestAndroid, 05_AppSignedRsaAuthentication_Passed) {
  // Set RSA PA ID for each Android Application files
  for (auto &path: kAndroidAppPathes) {
    std::string b64_five_signature;
    std::string b64_pa_signature;
    Signing::GetXattr(path, "security.five", b64_five_signature);
    Signing::PaAndroidSignature("com.samsung.androidpaclient", b64_five_signature, b64_pa_signature);
    Signing::SetXattr(path, "user.pa", b64_pa_signature);
  }

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  int res;
  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_EQ(0, res);
}

TEST_F(AuthenticateTestAndroid, 06_AppIncorrectFiveSignatureAuthentication_Failed) {
  // Set incorrect FIVE signature for each Android Application files
  for (auto &path: kAndroidAppPathes) {
    std::string b64_pa_signature;
    Signing::PaAndroidSignature("com.samsung.androidpaclient", "Z2FyYmFnZQo=", b64_pa_signature);
    Signing::SetXattr(path, "user.pa", b64_pa_signature);
  }

  Adb::Shell("am start -n com.samsung.androidpaclient/.MainActivity");

  int res;
  ASSERT_TRUE(CheckAuthenticationResult(&res));
  ASSERT_NE(0, res);
}