/*
 * Copyright (C) 2019 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 "biometrics_face_hidl_hal_test"

#include <android/hardware/biometrics/face/1.0/IBiometricsFace.h>
#include <android/hardware/biometrics/face/1.0/IBiometricsFaceClientCallback.h>

#include <VtsHalHidlTargetCallbackBase.h>
#include <VtsHalHidlTargetTestBase.h>
#include <VtsHalHidlTargetTestEnvBase.h>
#include <android-base/logging.h>

#include <chrono>
#include <cstdint>
#include <random>

using android::sp;
using android::hardware::hidl_vec;
using android::hardware::Return;
using android::hardware::Void;
using android::hardware::biometrics::face::V1_0::FaceAcquiredInfo;
using android::hardware::biometrics::face::V1_0::FaceError;
using android::hardware::biometrics::face::V1_0::Feature;
using android::hardware::biometrics::face::V1_0::IBiometricsFace;
using android::hardware::biometrics::face::V1_0::IBiometricsFaceClientCallback;
using android::hardware::biometrics::face::V1_0::OptionalBool;
using android::hardware::biometrics::face::V1_0::OptionalUint64;
using android::hardware::biometrics::face::V1_0::Status;

namespace {

// Arbitrary, nonexistent userId
constexpr uint32_t kUserId = 9;
// Arbitrary, nonexistent faceId
constexpr uint32_t kFaceId = 5;
constexpr uint32_t kTimeoutSec = 3;
constexpr auto kTimeout = std::chrono::seconds(kTimeoutSec);
constexpr int kGenerateChallengeIterations = 10;
constexpr char kFacedataDir[] = "/data/vendor_de/0/facedata";
constexpr char kCallbackNameOnEnrollResult[] = "onEnrollResult";
constexpr char kCallbackNameOnAuthenticated[] = "onAuthenticated";
constexpr char kCallbackNameOnAcquired[] = "onAcquired";
constexpr char kCallbackNameOnError[] = "onError";
constexpr char kCallbackNameOnRemoved[] = "onRemoved";
constexpr char kCallbackNameOnEnumerate[] = "onEnumerate";
constexpr char kCallbackNameOnLockoutChanged[] = "onLockoutChanged";

// Callback arguments that need to be captured for the tests.
struct FaceCallbackArgs {
    // The error passed to the last onError() callback.
    FaceError error;

    // The userId passed to the last onRemoved() callback.
    int32_t userId;
};

// Test callback class for the BiometricsFace HAL.
// The HAL will call these callback methods to notify about completed operations
// or encountered errors.
class FaceCallback : public ::testing::VtsHalHidlTargetCallbackBase<FaceCallbackArgs>,
                     public IBiometricsFaceClientCallback {
  public:
    Return<void> onEnrollResult(uint64_t, uint32_t, int32_t, uint32_t) override {
        NotifyFromCallback(kCallbackNameOnEnrollResult);
        return Void();
    }

    Return<void> onAuthenticated(uint64_t, uint32_t, int32_t, const hidl_vec<uint8_t>&) override {
        NotifyFromCallback(kCallbackNameOnAuthenticated);
        return Void();
    }

    Return<void> onAcquired(uint64_t, int32_t, FaceAcquiredInfo, int32_t) override {
        NotifyFromCallback(kCallbackNameOnAcquired);
        return Void();
    }

    Return<void> onError(uint64_t, int32_t, FaceError error, int32_t) override {
        FaceCallbackArgs args = {};
        args.error = error;
        NotifyFromCallback(kCallbackNameOnError, args);
        return Void();
    }

    Return<void> onRemoved(uint64_t, const hidl_vec<uint32_t>&, int32_t userId) override {
        FaceCallbackArgs args = {};
        args.userId = userId;
        NotifyFromCallback(kCallbackNameOnRemoved, args);
        return Void();
    }

    Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t) override {
        NotifyFromCallback(kCallbackNameOnEnumerate);
        return Void();
    }

    Return<void> onLockoutChanged(uint64_t) override {
        NotifyFromCallback(kCallbackNameOnLockoutChanged);
        return Void();
    }
};

// Test environment for the BiometricsFace HAL.
class FaceHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase {
  public:
    // Get the test environment singleton.
    static FaceHidlEnvironment* Instance() {
        static FaceHidlEnvironment* instance = new FaceHidlEnvironment;
        return instance;
    }

    void registerTestServices() override { registerTestService<IBiometricsFace>(); }

  private:
    FaceHidlEnvironment() = default;
};

// Test class for the BiometricsFace HAL.
class FaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
  public:
    void SetUp() override {
        mService = ::testing::VtsHalHidlTargetTestBase::getService<IBiometricsFace>(
                FaceHidlEnvironment::Instance()->getServiceName<IBiometricsFace>());
        ASSERT_NE(mService, nullptr);
        mCallback = new FaceCallback();
        mCallback->SetWaitTimeoutDefault(kTimeout);
        Return<void> ret1 = mService->setCallback(mCallback, [](const OptionalUint64& res) {
            ASSERT_EQ(Status::OK, res.status);
            // Makes sure the "deviceId" represented by "res.value" is not 0.
            // 0 would mean the HIDL is not available.
            ASSERT_NE(0UL, res.value);
        });
        ASSERT_TRUE(ret1.isOk());
        Return<Status> ret2 = mService->setActiveUser(kUserId, kFacedataDir);
        ASSERT_EQ(Status::OK, static_cast<Status>(ret2));
    }

    void TearDown() override {}

    sp<IBiometricsFace> mService;
    sp<FaceCallback> mCallback;
};

// generateChallenge should always return a unique, cryptographically secure,
// non-zero number.
TEST_F(FaceHidlTest, GenerateChallengeTest) {
    std::map<uint64_t, int> m;
    for (int i = 0; i < kGenerateChallengeIterations; ++i) {
        Return<void> ret =
                mService->generateChallenge(kTimeoutSec, [&m](const OptionalUint64& res) {
                    ASSERT_EQ(Status::OK, res.status);
                    EXPECT_NE(0UL, res.value);
                    m[res.value]++;
                    EXPECT_EQ(1UL, m[res.value]);
                });
        ASSERT_TRUE(ret.isOk());
    }
}

// enroll with an invalid (all zeroes) HAT should fail.
TEST_F(FaceHidlTest, EnrollZeroHatTest) {
    // Filling HAT with zeros
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; i++) {
        token[i] = 0;
    }

    Return<Status> ret = mService->enroll(token, kTimeoutSec, {});
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));

    // onError should be called with a meaningful (nonzero) error.
    auto res = mCallback->WaitForCallback(kCallbackNameOnError);
    EXPECT_TRUE(res.no_timeout);
    EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error);
}

// enroll with an invalid HAT should fail.
TEST_F(FaceHidlTest, EnrollGarbageHatTest) {
    // Filling HAT with pseudorandom invalid data.
    // Using default seed to make the test reproducible.
    std::mt19937 gen(std::mt19937::default_seed);
    std::uniform_int_distribution<uint8_t> dist;
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; ++i) {
        token[i] = dist(gen);
    }

    Return<Status> ret = mService->enroll(token, kTimeoutSec, {});
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));

    // onError should be called with a meaningful (nonzero) error.
    auto res = mCallback->WaitForCallback(kCallbackNameOnError);
    EXPECT_TRUE(res.no_timeout);
    EXPECT_EQ(FaceError::UNABLE_TO_PROCESS, res.args->error);
}

// setFeature with an invalid (all zeros) HAT should fail.
TEST_F(FaceHidlTest, SetFeatureZeroHatTest) {
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; i++) {
        token[i] = 0;
    }

    Return<Status> ret = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token, 0);
    ASSERT_EQ(Status::ILLEGAL_ARGUMENT, static_cast<Status>(ret));
}

// setFeature with an invalid HAT should fail.
TEST_F(FaceHidlTest, SetFeatureGarbageHatTest) {
    // Filling HAT with pseudorandom invalid data.
    // Using default seed to make the test reproducible.
    std::mt19937 gen(std::mt19937::default_seed);
    std::uniform_int_distribution<uint8_t> dist;
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; ++i) {
        token[i] = dist(gen);
    }

    Return<Status> ret = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token, 0);
    ASSERT_EQ(Status::ILLEGAL_ARGUMENT, static_cast<Status>(ret));
}

void assertGetFeatureFails(const sp<IBiometricsFace>& service, uint32_t faceId, Feature feature) {
    // Features cannot be retrieved for invalid faces.
    Return<void> res = service->getFeature(feature, faceId, [](const OptionalBool& result) {
        ASSERT_EQ(Status::ILLEGAL_ARGUMENT, result.status);
    });
    ASSERT_TRUE(res.isOk());
}

TEST_F(FaceHidlTest, GetFeatureRequireAttentionTest) {
    assertGetFeatureFails(mService, 0 /* faceId */, Feature::REQUIRE_ATTENTION);
}

TEST_F(FaceHidlTest, GetFeatureRequireDiversityTest) {
    assertGetFeatureFails(mService, 0 /* faceId */, Feature::REQUIRE_DIVERSITY);
}

// revokeChallenge should always return within the timeout
TEST_F(FaceHidlTest, RevokeChallengeTest) {
    auto start = std::chrono::system_clock::now();
    Return<Status> ret = mService->revokeChallenge();
    auto elapsed = std::chrono::system_clock::now() - start;
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
    ASSERT_GE(kTimeout, elapsed);
}

// The call to getAuthenticatorId should succeed.
TEST_F(FaceHidlTest, GetAuthenticatorIdTest) {
    Return<void> ret = mService->getAuthenticatorId(
            [](const OptionalUint64& res) { ASSERT_EQ(Status::OK, res.status); });
    ASSERT_TRUE(ret.isOk());
}

// The call to enumerate should succeed.
TEST_F(FaceHidlTest, EnumerateTest) {
    Return<Status> ret = mService->enumerate();
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
    auto res = mCallback->WaitForCallback(kCallbackNameOnEnumerate);
    EXPECT_TRUE(res.no_timeout);
}

// The call to remove should succeed for any faceId
TEST_F(FaceHidlTest, RemoveFaceTest) {
    // Remove a face
    Return<Status> ret = mService->remove(kFaceId);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
}

// Remove should accept 0 to delete all faces
TEST_F(FaceHidlTest, RemoveAllFacesTest) {
    // Remove all faces
    Return<Status> ret = mService->remove(0);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
}

// Active user should successfully set to a writable location.
TEST_F(FaceHidlTest, SetActiveUserTest) {
    // Create an active user
    Return<Status> ret = mService->setActiveUser(2, kFacedataDir);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));

    // Reset active user
    ret = mService->setActiveUser(kUserId, kFacedataDir);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
}

// Active user should fail to set to an unwritable location.
TEST_F(FaceHidlTest, SetActiveUserUnwritableTest) {
    // Create an active user to an unwritable location (device root dir)
    Return<Status> ret = mService->setActiveUser(3, "/");
    ASSERT_NE(Status::OK, static_cast<Status>(ret));

    // Reset active user
    ret = mService->setActiveUser(kUserId, kFacedataDir);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
}

// Active user should fail to set to a null location.
TEST_F(FaceHidlTest, SetActiveUserNullTest) {
    // Create an active user to a null location.
    Return<Status> ret = mService->setActiveUser(4, nullptr);
    ASSERT_NE(Status::OK, static_cast<Status>(ret));

    // Reset active user
    ret = mService->setActiveUser(kUserId, kFacedataDir);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
}

// Cancel should always return CANCELED from any starting state including
// the IDLE state.
TEST_F(FaceHidlTest, CancelTest) {
    Return<Status> ret = mService->cancel();
    // check that we were able to make an IPC request successfully
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));
    auto res = mCallback->WaitForCallback(kCallbackNameOnError);
    // make sure callback was invoked within kRevokeChallengeTimeout
    EXPECT_TRUE(res.no_timeout);
    EXPECT_EQ(FaceError::CANCELED, res.args->error);
}

TEST_F(FaceHidlTest, OnLockoutChangedTest) {
    // Update active user and ensure onLockoutChanged was called.
    Return<Status> ret = mService->setActiveUser(kUserId + 1, kFacedataDir);
    ASSERT_EQ(Status::OK, static_cast<Status>(ret));

    // Make sure callback was invoked
    auto res = mCallback->WaitForCallback(kCallbackNameOnLockoutChanged);
    EXPECT_TRUE(res.no_timeout);
}

}  // anonymous namespace

int main(int argc, char** argv) {
    ::testing::AddGlobalTestEnvironment(FaceHidlEnvironment::Instance());
    ::testing::InitGoogleTest(&argc, argv);
    FaceHidlEnvironment::Instance()->init(&argc, argv);
    int status = RUN_ALL_TESTS();
    LOG(INFO) << "Test result = " << status;
    return status;
}