Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a34e407 authored by Ilya Matyukhin's avatar Ilya Matyukhin
Browse files

Implemented VTS tests for BiometricsFace.hal

Test: manually built and ran the tests
Fixes: 110708105
Change-Id: Id2882436b2346032b080cee16a9fc289ee3dbdc2
parent b098cb0d
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
//
// 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.
//

cc_test {
    name: "VtsHalBiometricsFaceV1_0TargetTest",
    defaults: ["VtsHalTargetTestDefaults"],
    srcs: ["VtsHalBiometricsFaceV1_0TargetTest.cpp"],
    static_libs: ["android.hardware.biometrics.face@1.0"],
    test_suites: ["general-tests"],
}
+412 −0
Original line number Diff line number Diff line
/*
 * 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 "face_hidl_test"

#include <VtsHalHidlTargetTestBase.h>
#include <VtsHalHidlTargetTestEnvBase.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android/hardware/biometrics/face/1.0/IBiometricsFace.h>
#include <android/hardware/biometrics/face/1.0/IBiometricsFaceClientCallback.h>
#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/Condition.h>

#include <cinttypes>
#include <cstdint>
#include <future>
#include <utility>

using android::Condition;
using android::Mutex;
using android::sp;
using android::base::GetUintProperty;
using android::hardware::hidl_vec;
using android::hardware::Return;
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::OptionalUint64;
using android::hardware::biometrics::face::V1_0::Status;

namespace {

const uint32_t kTimeout = 3;
const std::chrono::seconds kTimeoutInSeconds = std::chrono::seconds(kTimeout);
const uint32_t kUserId = 99;
const uint32_t kFaceId = 5;
const char kTmpDir[] = "/data/system/users/0/facedata";
const int kIterations = 1000;

const auto kAssertCallbackIsSet = [](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);
};

// Wait for a callback to occur (signaled by the given future) up to the
// provided timeout. If the future is invalid or the callback does not come
// within the given time, returns false.
template <class ReturnType>
bool waitForCallback(std::future<ReturnType> future,
                     std::chrono::milliseconds timeout = kTimeoutInSeconds) {
    auto expiration = std::chrono::system_clock::now() + timeout;
    EXPECT_TRUE(future.valid());
    if (future.valid()) {
        std::future_status status = future.wait_until(expiration);
        EXPECT_NE(std::future_status::timeout, status) << "Timed out waiting for callback";
        if (status == std::future_status::ready) {
            return true;
        }
    }
    return false;
}

// Base callback implementation that just logs all callbacks by default
class FaceCallbackBase : public IBiometricsFaceClientCallback {
  public:
    Return<void> onEnrollResult(uint64_t, uint32_t, int32_t, uint32_t) override {
        ALOGD("Enroll callback called.");
        return Return<void>();
    }

    Return<void> onAuthenticated(uint64_t, uint32_t, int32_t, const hidl_vec<uint8_t>&) override {
        ALOGD("Authenticated callback called.");
        return Return<void>();
    }

    Return<void> onAcquired(uint64_t, int32_t, FaceAcquiredInfo, int32_t) override {
        ALOGD("Acquired callback called.");
        return Return<void>();
    }

    Return<void> onError(uint64_t, int32_t, FaceError, int32_t) override {
        ALOGD("Error callback called.");
        EXPECT_TRUE(false);  // fail any test that triggers an error
        return Return<void>();
    }

    Return<void> onRemoved(uint64_t, uint32_t, int32_t, uint32_t) override {
        ALOGD("Removed callback called.");
        return Return<void>();
    }

    Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t /* userId */) override {
        ALOGD("Enumerate callback called.");
        return Return<void>();
    }

    Return<void> onLockoutChanged(uint64_t) override {
        ALOGD("LockoutChanged callback called.");
        return Return<void>();
    }
};

class EnumerateCallback : public FaceCallbackBase {
  public:
    Return<void> onEnumerate(uint64_t, const hidl_vec<uint32_t>&, int32_t) override {
        promise.set_value();
        return Return<void>();
    }

    std::promise<void> promise;
};

class ErrorCallback : public FaceCallbackBase {
  public:
    ErrorCallback(bool filterErrors = false, FaceError errorType = FaceError::HW_UNAVAILABLE)
        : filterErrors(filterErrors), errorType(errorType), hasError(false) {}

    Return<void> onError(uint64_t, int32_t, FaceError error, int32_t) override {
        if ((filterErrors && errorType == error) || !filterErrors) {
            hasError = true;
            this->error = error;
            promise.set_value();
        }
        return Return<void>();
    }

    bool filterErrors;
    FaceError errorType;
    bool hasError;
    FaceError error;
    std::promise<void> promise;
};

class RemoveCallback : public FaceCallbackBase {
  public:
    explicit RemoveCallback(int32_t userId) : removeUserId(userId) {}

    Return<void> onRemoved(uint64_t, uint32_t, int32_t userId, uint32_t remaining) override {
        EXPECT_EQ(removeUserId, userId);
        promise.set_value();
        if (remaining == 0UL) {
            promise.set_value();
        }
        return Return<void>();
    }

    int32_t removeUserId;
    std::promise<void> promise;
};

// Test environment for Face HIDL 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>(); }
};

class FaceHidlTest : public ::testing::VtsHalHidlTargetTestBase {
  public:
    void SetUp() override {
        mService = ::testing::VtsHalHidlTargetTestBase::getService<IBiometricsFace>(
                FaceHidlEnvironment::Instance()->getServiceName<IBiometricsFace>());
        ASSERT_FALSE(mService == nullptr);
        Return<Status> res = mService->setActiveUser(kUserId, kTmpDir);
        ASSERT_EQ(Status::OK, static_cast<Status>(res));
    }

    void TearDown() override {}

    sp<IBiometricsFace> mService;
};

// The service should be reachable.
TEST_F(FaceHidlTest, ConnectTest) {
    sp<FaceCallbackBase> cb = new FaceCallbackBase();
    mService->setCallback(cb, kAssertCallbackIsSet);
}

// Starting the service with null callback should succeed.
TEST_F(FaceHidlTest, ConnectNullTest) {
    mService->setCallback(nullptr, kAssertCallbackIsSet);
}

// 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 < kIterations; ++i) {
        mService->generateChallenge(kTimeout, [&m](const OptionalUint64& res) {
            ASSERT_EQ(Status::OK, res.status);
            EXPECT_NE(0UL, res.value);
            m[res.value]++;
            EXPECT_EQ(1UL, m[res.value]);
        });
    }
}

// enroll with an invalid (all zeroes) HAT should fail.
TEST_F(FaceHidlTest, EnrollZeroHatTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; i++) {
        token[i] = 0;
    }

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

    // At least one call to onError should occur
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
    ASSERT_TRUE(cb->hasError);
}

// enroll with an invalid HAT should fail.
TEST_F(FaceHidlTest, EnrollGarbageHatTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    // Filling HAT with invalid data
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; ++i) {
        token[i] = i;
    }

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

    // At least one call to onError should occur
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
    ASSERT_TRUE(cb->hasError);
}

// setFeature with an invalid (all zeros) HAT should fail.
TEST_F(FaceHidlTest, SetFeatureZeroHatTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; i++) {
        token[i] = 0;
    }

    Return<Status> res = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token);
    ASSERT_EQ(Status::OK, static_cast<Status>(res));

    // At least one call to onError should occur
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
    ASSERT_TRUE(cb->hasError);
}

// setFeature with an invalid HAT should fail.
TEST_F(FaceHidlTest, SetFeatureGarbageHatTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    // Filling HAT with invalid data
    hidl_vec<uint8_t> token(69);
    for (size_t i = 0; i < 69; ++i) {
        token[i] = i;
    }

    Return<Status> res = mService->setFeature(Feature::REQUIRE_DIVERSITY, false, token);
    ASSERT_EQ(Status::OK, static_cast<Status>(res));

    // At least one call to onError should occur
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
    ASSERT_TRUE(cb->hasError);
}

// getFeature by default should return true for REQUIRE_ATTENTION.
TEST_F(FaceHidlTest, GetFeatureRequireAttentionTest) {
    Return<bool> res = mService->getFeature(Feature::REQUIRE_ATTENTION);
    ASSERT_EQ(true, static_cast<bool>(res));
}

// getFeature by default should return true for REQUIRE_DIVERSITY.
TEST_F(FaceHidlTest, GetFeatureRequireDiversityTest) {
    Return<bool> res = mService->getFeature(Feature::REQUIRE_DIVERSITY);
    ASSERT_EQ(true, static_cast<bool>(res));
}

// revokeChallenge should always return within the timeout
TEST_F(FaceHidlTest, RevokeChallengeTest) {
    sp<FaceCallbackBase> cb = new FaceCallbackBase();
    mService->setCallback(cb, kAssertCallbackIsSet);

    auto start = std::chrono::system_clock::now();
    mService->revokeChallenge();
    auto elapsed = std::chrono::system_clock::now() - start;
    ASSERT_GE(kTimeoutInSeconds, elapsed);
}

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

// The call to enumerate should succeed.
TEST_F(FaceHidlTest, EnumerateTest) {
    sp<EnumerateCallback> cb = new EnumerateCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);
    Return<Status> res = mService->enumerate();
    ASSERT_EQ(Status::OK, static_cast<Status>(res));
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
}

// The call to remove should succeed for any faceId
TEST_F(FaceHidlTest, RemoveFaceTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    // Remove a face
    Return<Status> res = mService->remove(kFaceId);
    ASSERT_EQ(Status::OK, static_cast<Status>(res));
}

// Remove should accept 0 to delete all faces
TEST_F(FaceHidlTest, RemoveAllFacesTest) {
    sp<ErrorCallback> cb = new ErrorCallback();
    mService->setCallback(cb, kAssertCallbackIsSet);

    // Remove all faces
    Return<Status> res = mService->remove(0);
    ASSERT_EQ(Status::OK, static_cast<Status>(res));
}

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

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

// 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> res = mService->setActiveUser(3, "/");
    ASSERT_NE(Status::OK, static_cast<Status>(res));

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

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

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

// Cancel should always return CANCELED from any starting state including
// the IDLE state.
TEST_F(FaceHidlTest, CancelTest) {
    sp<ErrorCallback> cb = new ErrorCallback(true, FaceError::CANCELED);
    mService->setCallback(cb, kAssertCallbackIsSet);

    Return<Status> res = mService->cancel();
    // check that we were able to make an IPC request successfully
    ASSERT_EQ(Status::OK, static_cast<Status>(res));

    // make sure callback was invoked within kTimeoutInSeconds
    ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
    // check error should be CANCELED
    ASSERT_EQ(FaceError::CANCELED, cb->error);
}

}  // 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;
}