You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
16 KiB
462 lines
16 KiB
/* |
|
* 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/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprint.h> |
|
#include <android/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprintClientCallback.h> |
|
#include <hidl/HidlSupport.h> |
|
#include <hidl/HidlTransportSupport.h> |
|
#include <VtsHalHidlTargetTestBase.h> |
|
|
|
#include <cinttypes> |
|
#include <future> |
|
#include <utility> |
|
|
|
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 const std::string kTmpDir = "/data/system/"; |
|
static const uint32_t kIterations = 1000; |
|
|
|
// 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::VtsHalHidlTargetTestBase { |
|
public: |
|
virtual void SetUp() override { |
|
mService = ::testing::VtsHalHidlTargetTestBase::getService<IBiometricsFingerprint>(); |
|
ASSERT_FALSE(mService == nullptr); |
|
|
|
// Create an active group |
|
// FP service can only write to /data/system due to |
|
// SELinux Policy and Linux Dir Permissions |
|
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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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_F(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 |
|
|
|
int main(int argc, char **argv) { |
|
::testing::InitGoogleTest(&argc, argv); |
|
int status = RUN_ALL_TESTS(); |
|
LOG(INFO) << "Test result = " << status; |
|
return status; |
|
} |
|
|
|
|