480 lines
17 KiB
C++
480 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2017 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 "fingerprint_hidl_hal_test"
|
|
|
|
#include <android-base/logging.h>
|
|
#include <android-base/properties.h>
|
|
#include <android/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprint.h>
|
|
#include <android/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprintClientCallback.h>
|
|
#include <gtest/gtest.h>
|
|
#include <hidl/GtestPrinter.h>
|
|
#include <hidl/HidlSupport.h>
|
|
#include <hidl/HidlTransportSupport.h>
|
|
#include <hidl/ServiceManagement.h>
|
|
#include <utils/Condition.h>
|
|
|
|
#include <cinttypes>
|
|
#include <future>
|
|
#include <utility>
|
|
|
|
using android::base::GetUintProperty;
|
|
using android::Condition;
|
|
using android::hardware::biometrics::fingerprint::V2_1::IBiometricsFingerprint;
|
|
using android::hardware::biometrics::fingerprint::V2_1::IBiometricsFingerprintClientCallback;
|
|
using android::hardware::biometrics::fingerprint::V2_1::FingerprintAcquiredInfo;
|
|
using android::hardware::biometrics::fingerprint::V2_1::FingerprintError;
|
|
using android::hardware::biometrics::fingerprint::V2_1::RequestStatus;
|
|
using android::hardware::hidl_vec;
|
|
using android::hardware::Return;
|
|
using android::Mutex;
|
|
using android::sp;
|
|
|
|
namespace {
|
|
|
|
static const uint32_t kTimeout = 3;
|
|
static const std::chrono::seconds kTimeoutInSeconds = std::chrono::seconds(kTimeout);
|
|
static const uint32_t kGroupId = 99;
|
|
static std::string kTmpDir = "";
|
|
static const uint32_t kIterations = 10;
|
|
|
|
// 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 FingerprintCallbackBase : public IBiometricsFingerprintClientCallback {
|
|
public:
|
|
// implement methods of IBiometricsFingerprintClientCallback
|
|
virtual Return<void> onEnrollResult(uint64_t, uint32_t, uint32_t, uint32_t)
|
|
override {
|
|
ALOGD("Enroll callback called.");
|
|
return Return<void>();
|
|
}
|
|
|
|
virtual Return<void> onAcquired(uint64_t, FingerprintAcquiredInfo, int32_t)
|
|
override {
|
|
ALOGD("Acquired callback called.");
|
|
return Return<void>();
|
|
}
|
|
|
|
virtual Return<void> onAuthenticated(uint64_t, uint32_t, uint32_t,
|
|
const hidl_vec<uint8_t>&) override {
|
|
ALOGD("Authenticated callback called.");
|
|
return Return<void>();
|
|
}
|
|
|
|
virtual Return<void> onError(uint64_t, FingerprintError, int32_t)
|
|
override {
|
|
ALOGD("Error callback called.");
|
|
EXPECT_TRUE(false); // fail any test that triggers an error
|
|
return Return<void>();
|
|
}
|
|
|
|
virtual Return<void> onRemoved(uint64_t, uint32_t, uint32_t, uint32_t)
|
|
override {
|
|
ALOGD("Removed callback called.");
|
|
return Return<void>();
|
|
}
|
|
|
|
virtual Return<void> onEnumerate(uint64_t, uint32_t, uint32_t, uint32_t)
|
|
override {
|
|
ALOGD("Enumerate callback called.");
|
|
return Return<void>();
|
|
}
|
|
};
|
|
|
|
class EnumerateCallback : public FingerprintCallbackBase {
|
|
public:
|
|
virtual Return<void> onEnumerate(uint64_t deviceId, uint32_t fingerId,
|
|
uint32_t groupId, uint32_t remaining) override {
|
|
this->deviceId = deviceId;
|
|
this->fingerId = fingerId;
|
|
this->groupId = groupId;
|
|
this->remaining = remaining;
|
|
|
|
if(remaining == 0UL) {
|
|
promise.set_value();
|
|
}
|
|
return Return<void>();
|
|
}
|
|
|
|
uint64_t deviceId;
|
|
uint32_t fingerId;
|
|
uint32_t groupId;
|
|
uint32_t remaining;
|
|
std::promise<void> promise;
|
|
};
|
|
|
|
class ErrorCallback : public FingerprintCallbackBase {
|
|
public:
|
|
ErrorCallback(
|
|
bool filterErrors=false,
|
|
FingerprintError errorType=FingerprintError::ERROR_NO_ERROR) {
|
|
this->filterErrors = filterErrors;
|
|
this->errorType = errorType;
|
|
}
|
|
|
|
virtual Return<void> onError(uint64_t deviceId, FingerprintError error,
|
|
int32_t vendorCode) override {
|
|
if ((this->filterErrors && this->errorType == error) || !this->filterErrors) {
|
|
this->deviceId = deviceId;
|
|
this->error = error;
|
|
this->vendorCode = vendorCode;
|
|
promise.set_value();
|
|
}
|
|
return Return<void>();
|
|
}
|
|
|
|
bool filterErrors;
|
|
FingerprintError errorType;
|
|
uint64_t deviceId;
|
|
FingerprintError error;
|
|
int32_t vendorCode;
|
|
std::promise<void> promise;
|
|
};
|
|
|
|
class RemoveCallback : public FingerprintCallbackBase {
|
|
public:
|
|
RemoveCallback(uint32_t groupId) {
|
|
this->removeGroupId = groupId;
|
|
}
|
|
|
|
virtual Return<void> onRemoved(uint64_t, uint32_t, uint32_t groupId,
|
|
uint32_t remaining) override {
|
|
EXPECT_EQ(this->removeGroupId, groupId);
|
|
if(remaining == 0UL) {
|
|
promise.set_value();
|
|
}
|
|
return Return<void>();
|
|
}
|
|
|
|
uint32_t removeGroupId;
|
|
std::promise<void> promise;
|
|
};
|
|
|
|
class FingerprintHidlTest : public ::testing::TestWithParam<std::string> {
|
|
public:
|
|
virtual void SetUp() override {
|
|
mService = IBiometricsFingerprint::getService(GetParam());
|
|
ASSERT_FALSE(mService == nullptr);
|
|
|
|
/*
|
|
* Devices shipped from now on will instead store
|
|
* fingerprint data under /data/vendor_de/<user-id>/fpdata.
|
|
* Support for /data/vendor_de and /data/vendor_ce has been added to vold.
|
|
*/
|
|
|
|
uint64_t api_level = GetUintProperty<uint64_t>("ro.product.first_api_level", 0);
|
|
if (api_level == 0) {
|
|
api_level = GetUintProperty<uint64_t>("ro.build.version.sdk", 0);
|
|
}
|
|
ASSERT_TRUE(api_level != 0);
|
|
|
|
// 27 is the API number for O-MR1
|
|
if (api_level <= 27) {
|
|
kTmpDir = "/data/system/users/0/fpdata/";
|
|
} else {
|
|
kTmpDir = "/data/vendor_de/0/fpdata/";
|
|
}
|
|
|
|
Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
}
|
|
|
|
virtual void TearDown() override {}
|
|
|
|
sp<IBiometricsFingerprint> mService;
|
|
};
|
|
|
|
// The service should be reachable.
|
|
TEST_P(FingerprintHidlTest, ConnectTest) {
|
|
sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase();
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
}
|
|
|
|
// Starting the service with null callback should succeed.
|
|
TEST_P(FingerprintHidlTest, ConnectNullTest) {
|
|
Return<uint64_t> rc = mService->setNotify(NULL);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
}
|
|
|
|
// Pre-enroll should always return unique, cryptographically secure, non-zero number
|
|
TEST_P(FingerprintHidlTest, PreEnrollTest) {
|
|
std::map<uint64_t, uint64_t> m;
|
|
|
|
for (unsigned int i = 0; i < kIterations; ++i) {
|
|
uint64_t res = static_cast<uint64_t>(mService->preEnroll());
|
|
EXPECT_NE(0UL, res);
|
|
m[res]++;
|
|
EXPECT_EQ(1UL, m[res]);
|
|
}
|
|
}
|
|
|
|
// Enroll with an invalid (all zeroes) HAT should fail.
|
|
TEST_P(FingerprintHidlTest, EnrollInvalidHatTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback();
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
uint8_t token[69];
|
|
for (int i = 0; i < 69; i++) {
|
|
token[i] = 0;
|
|
}
|
|
|
|
Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// At least one call to onError should occur
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error);
|
|
}
|
|
|
|
// Enroll with an invalid (null) HAT should fail.
|
|
TEST_P(FingerprintHidlTest, EnrollNullTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback();
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
uint8_t token[69];
|
|
Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// At least one call to onError should occur
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error);
|
|
}
|
|
|
|
// PostEnroll should always return within 3s
|
|
TEST_P(FingerprintHidlTest, PostEnrollTest) {
|
|
sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase();
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
|
|
auto start = std::chrono::system_clock::now();
|
|
Return<RequestStatus> res = mService->postEnroll();
|
|
auto elapsed = std::chrono::system_clock::now() - start;
|
|
ASSERT_GE(kTimeoutInSeconds, elapsed);
|
|
}
|
|
|
|
// getAuthenticatorId should always return non-zero numbers
|
|
TEST_P(FingerprintHidlTest, GetAuthenticatorIdTest) {
|
|
Return<uint64_t> res = mService->getAuthenticatorId();
|
|
EXPECT_NE(0UL, static_cast<uint64_t>(res));
|
|
}
|
|
|
|
// Enumerate should always trigger onEnumerated(fid=0, rem=0) when there are no fingerprints
|
|
TEST_P(FingerprintHidlTest, EnumerateTest) {
|
|
sp<EnumerateCallback> cb = new EnumerateCallback();
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
// Callback will return when rem=0 is found
|
|
Return<RequestStatus> res = mService->enumerate();
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
EXPECT_EQ(0UL, cb->fingerId);
|
|
EXPECT_EQ(0UL, cb->remaining);
|
|
}
|
|
|
|
// Remove should succeed on any inputs
|
|
// At least one callback with "remaining=0" should occur
|
|
TEST_P(FingerprintHidlTest, RemoveFingerprintTest) {
|
|
// Register callback
|
|
sp<RemoveCallback> cb = new RemoveCallback(kGroupId);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
// Remove a fingerprint
|
|
Return<RequestStatus> res = mService->remove(kGroupId, 1);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// At least one call to onRemove with remaining=0 should occur
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
}
|
|
|
|
// Remove should accept 0 to delete all fingerprints
|
|
// At least one callback with "remaining=0" should occur.
|
|
TEST_P(FingerprintHidlTest, RemoveAllFingerprintsTest) {
|
|
// Register callback
|
|
sp<RemoveCallback> cb = new RemoveCallback(kGroupId);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
// Remove all fingerprints
|
|
Return<RequestStatus> res = mService->remove(kGroupId, 0);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
}
|
|
|
|
// Active group should successfully set to a writable location.
|
|
TEST_P(FingerprintHidlTest, SetActiveGroupTest) {
|
|
// Create an active group
|
|
Return<RequestStatus> res = mService->setActiveGroup(2, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// Reset active group
|
|
res = mService->setActiveGroup(kGroupId, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
}
|
|
|
|
// Active group should fail to set to an unwritable location.
|
|
TEST_P(FingerprintHidlTest, SetActiveGroupUnwritableTest) {
|
|
// Create an active group to an unwritable location (device root dir)
|
|
Return<RequestStatus> res = mService->setActiveGroup(3, "/");
|
|
ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// Reset active group
|
|
res = mService->setActiveGroup(kGroupId, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
}
|
|
|
|
// Active group should fail to set to a null location.
|
|
TEST_P(FingerprintHidlTest, SetActiveGroupNullTest) {
|
|
// Create an active group to a null location.
|
|
Return<RequestStatus> res = mService->setActiveGroup(4, nullptr);
|
|
ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// Reset active group
|
|
res = mService->setActiveGroup(kGroupId, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
}
|
|
|
|
// Cancel should always return ERROR_CANCELED from any starting state including
|
|
// the IDLE state.
|
|
TEST_P(FingerprintHidlTest, CancelTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0UL, static_cast<uint64_t>(rc));
|
|
|
|
Return<RequestStatus> res = mService->cancel();
|
|
// check that we were able to make an IPC request successfully
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// make sure callback was invoked within kTimeoutInSeconds
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
// check error should be ERROR_CANCELED
|
|
ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error);
|
|
}
|
|
|
|
// A call to cancel should succeed during enroll.
|
|
TEST_P(FingerprintHidlTest, CancelEnrollTest) {
|
|
Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0U, static_cast<uint64_t>(rc));
|
|
|
|
uint8_t token[69];
|
|
res = mService->enroll(token, kGroupId, kTimeout);
|
|
// check that we were able to make an IPC request successfully
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
res = mService->cancel();
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// make sure callback was invoked within kTimeoutInSeconds
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
|
|
// check error should be ERROR_CANCELED
|
|
ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error);
|
|
}
|
|
|
|
// A call to cancel should succeed during authentication.
|
|
TEST_P(FingerprintHidlTest, CancelAuthTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0U, static_cast<uint64_t>(rc));
|
|
|
|
Return<RequestStatus> res = mService->authenticate(0, kGroupId);
|
|
// check that we were able to make an IPC request successfully
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
res = mService->cancel();
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// make sure callback was invoked within kTimeoutInSeconds
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
|
|
// check error should be ERROR_CANCELED
|
|
ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error);
|
|
}
|
|
|
|
// A call to cancel should succeed during authentication.
|
|
TEST_P(FingerprintHidlTest, CancelRemoveTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0U, static_cast<uint64_t>(rc));
|
|
|
|
// Remove a fingerprint
|
|
Return<RequestStatus> res = mService->remove(kGroupId, 1);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
res = mService->cancel();
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// make sure callback was invoked within kTimeoutInSeconds
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
|
|
// check error should be ERROR_CANCELED
|
|
ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error);
|
|
}
|
|
|
|
// A call to cancel should succeed during authentication.
|
|
TEST_P(FingerprintHidlTest, CancelRemoveAllTest) {
|
|
sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED);
|
|
Return<uint64_t> rc = mService->setNotify(cb);
|
|
ASSERT_NE(0U, static_cast<uint64_t>(rc));
|
|
|
|
// Remove a fingerprint
|
|
Return<RequestStatus> res = mService->remove(kGroupId, 0);
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
res = mService->cancel();
|
|
ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res));
|
|
|
|
// make sure callback was invoked within kTimeoutInSeconds
|
|
ASSERT_TRUE(waitForCallback(cb->promise.get_future()));
|
|
|
|
// check error should be ERROR_CANCELED
|
|
ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error);
|
|
}
|
|
} // anonymous namespace
|
|
|
|
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FingerprintHidlTest);
|
|
INSTANTIATE_TEST_SUITE_P(PerInstance, FingerprintHidlTest,
|
|
testing::ValuesIn(android::hardware::getAllHalInstanceNames(
|
|
IBiometricsFingerprint::descriptor)),
|
|
android::hardware::PrintInstanceNameToString);
|