diff --git a/biometrics/face/aidl/default/Android.bp b/biometrics/face/aidl/default/Android.bp index 4816219251..4e8390a64e 100644 --- a/biometrics/face/aidl/default/Android.bp +++ b/biometrics/face/aidl/default/Android.bp @@ -30,6 +30,7 @@ cc_binary { "libnativewindow", ], srcs: [ + "FakeLockoutTracker.cpp", "main.cpp", "Face.cpp", "FakeFaceEngine.cpp", @@ -63,6 +64,33 @@ cc_test { srcs: [ "tests/FakeFaceEngineTest.cpp", "FakeFaceEngine.cpp", + "FakeLockoutTracker.cpp", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libnativewindow", + ], + include_dirs: [ + "frameworks/native/aidl/gui", + ], + static_libs: [ + "libandroid.hardware.biometrics.face.VirtualProps", + "android.hardware.biometrics.face-V4-ndk", + "android.hardware.biometrics.common-V4-ndk", + "android.hardware.keymaster-V4-ndk", + "android.hardware.biometrics.common.util", + ], + vendor: true, + test_suites: ["general-tests"], + require_root: true, +} + +cc_test { + name: "android.hardware.biometrics.face.FakeLockoutTrackerTest", + srcs: [ + "tests/FakeLockoutTrackerTest.cpp", + "FakeLockoutTracker.cpp", ], shared_libs: [ "libbase", diff --git a/biometrics/face/aidl/default/FakeFaceEngine.cpp b/biometrics/face/aidl/default/FakeFaceEngine.cpp index dc524bbbcf..7380611853 100644 --- a/biometrics/face/aidl/default/FakeFaceEngine.cpp +++ b/biometrics/face/aidl/default/FakeFaceEngine.cpp @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2023 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 "FaceVirtualHalEngine" + #include "FakeFaceEngine.h" #include @@ -186,6 +204,10 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI return; } + if (mLockoutTracker.checkIfLockout(cb)) { + return; + } + int i = 0; do { if (FaceHalProperties::lockout().value_or(false)) { @@ -197,6 +219,7 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { LOG(ERROR) << "Fail: operation_authenticate_fails"; + mLockoutTracker.addFailedAttempt(cb); cb->onAuthenticationFailed(); return; } @@ -231,10 +254,12 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI } while (!Util::hasElapsed(now, duration)); if (id > 0 && isEnrolled) { + mLockoutTracker.reset(); cb->onAuthenticationSucceeded(id, {} /* hat */); return; } else { LOG(ERROR) << "Fail: face not enrolled"; + mLockoutTracker.addFailedAttempt(cb); cb->onAuthenticationFailed(); cb->onError(Error::TIMEOUT, 0 /* vendorError*/); return; @@ -389,6 +414,7 @@ void FakeFaceEngine::resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) { BEGIN_OP(0); FaceHalProperties::lockout(false); + mLockoutTracker.reset(); cb->onLockoutCleared(); } diff --git a/biometrics/face/aidl/default/FakeFaceEngine.h b/biometrics/face/aidl/default/FakeFaceEngine.h index 06dd396c70..8d9303c491 100644 --- a/biometrics/face/aidl/default/FakeFaceEngine.h +++ b/biometrics/face/aidl/default/FakeFaceEngine.h @@ -16,18 +16,17 @@ #pragma once -#define LOG_TAG "FaceVirtualHal" - #include #include #include #include -#include - #include +#include #include +#include "FakeLockoutTracker.h" + namespace aidl::android::hardware::biometrics::face { namespace face = aidl::android::hardware::biometrics::face; @@ -39,6 +38,7 @@ using aidl::android::hardware::common::NativeHandle; class FakeFaceEngine { public: FakeFaceEngine() : mRandom(std::mt19937::default_seed) {} + virtual ~FakeFaceEngine() {} static face::FaceSensorType GetSensorType(); static common::SensorStrength GetSensorStrength(); @@ -61,6 +61,13 @@ class FakeFaceEngine { void invalidateAuthenticatorIdImpl(ISessionCallback* cb); void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/); + virtual std::string toString() const { + std::ostringstream os; + os << "----- FakeFaceEngine:: -----" << std::endl; + os << mLockoutTracker.toString(); + return os.str(); + } + std::mt19937 mRandom; private: @@ -68,6 +75,7 @@ class FakeFaceEngine { static constexpr int32_t FACE_ERROR_VENDOR_BASE = 1000; std::pair convertAcquiredInfo(int32_t code); std::pair convertError(int32_t code); + FakeLockoutTracker mLockoutTracker; }; } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/FakeLockoutTracker.cpp b/biometrics/face/aidl/default/FakeLockoutTracker.cpp new file mode 100644 index 0000000000..70bf08ee21 --- /dev/null +++ b/biometrics/face/aidl/default/FakeLockoutTracker.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 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 "FaceVirtualHalLockoutTracker" + +#include "FakeLockoutTracker.h" +#include +#include +#include "util/Util.h" + +using namespace ::android::face::virt; + +namespace aidl::android::hardware::biometrics::face { + +void FakeLockoutTracker::reset(bool dueToTimerExpire) { + if (!dueToTimerExpire) { + mFailedCount = 0; + mLastFailedTime = 0; + } + mTimedFailedCount = 0; + mCurrentMode = LockoutMode::kNone; + abortTimer(); +} + +void FakeLockoutTracker::addFailedAttempt(ISessionCallback* cb) { + bool lockoutEnabled = FaceHalProperties::lockout_enable().value_or(false); + bool timedLockoutenabled = FaceHalProperties::lockout_timed_enable().value_or(false); + if (lockoutEnabled) { + mFailedCount++; + mTimedFailedCount++; + mLastFailedTime = Util::getSystemNanoTime(); + int32_t lockoutTimedThreshold = FaceHalProperties::lockout_timed_threshold().value_or(3); + int32_t lockoutPermanetThreshold = + FaceHalProperties::lockout_permanent_threshold().value_or(5); + if (mFailedCount >= lockoutPermanetThreshold) { + mCurrentMode = LockoutMode::kPermanent; + LOG(ERROR) << "FakeLockoutTracker: lockoutPermanent"; + cb->onLockoutPermanent(); + abortTimer(); + } else if (timedLockoutenabled && mTimedFailedCount >= lockoutTimedThreshold) { + if (mCurrentMode == LockoutMode::kNone) { + mCurrentMode = LockoutMode::kTimed; + startLockoutTimer(getTimedLockoutDuration(), cb); + } + LOG(ERROR) << "FakeLockoutTracker: lockoutTimed"; + cb->onLockoutTimed(getLockoutTimeLeft()); + } + } else { + reset(); + } +} + +FakeLockoutTracker::LockoutMode FakeLockoutTracker::getMode() { + return mCurrentMode; +} + +int32_t FakeLockoutTracker::getTimedLockoutDuration() { + return FaceHalProperties::lockout_timed_duration().value_or(10 * 1000); +} + +int64_t FakeLockoutTracker::getLockoutTimeLeft() { + int64_t res = 0; + + if (mLastFailedTime > 0) { + auto now = Util::getSystemNanoTime(); + auto elapsed = (now - mLastFailedTime) / 1000000LL; + res = getTimedLockoutDuration() - elapsed; + LOG(INFO) << "elapsed=" << elapsed << " now = " << now + << " mLastFailedTime=" << mLastFailedTime << " res=" << res; + } + + return res; +} + +bool FakeLockoutTracker::checkIfLockout(ISessionCallback* cb) { + if (mCurrentMode == LockoutMode::kPermanent) { + LOG(ERROR) << "Lockout permanent"; + cb->onLockoutPermanent(); + return true; + } else if (mCurrentMode == LockoutMode::kTimed) { + auto timeLeft = getLockoutTimeLeft(); + LOG(ERROR) << "Lockout timed " << timeLeft; + cb->onLockoutTimed(timeLeft); + return true; + } + return false; +} + +void FakeLockoutTracker::startLockoutTimer(int64_t timeout, ISessionCallback* cb) { + LOG(ERROR) << "startLockoutTimer: to=" << timeout; + if (mIsLockoutTimerStarted) return; + std::function action = + std::bind(&FakeLockoutTracker::lockoutTimerExpired, this, std::placeholders::_1); + std::thread([timeout, action, cb]() { + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + action(cb); + }).detach(); + + mIsLockoutTimerStarted = true; +} + +void FakeLockoutTracker::lockoutTimerExpired(ISessionCallback* cb) { + LOG(INFO) << "lockout timer expired"; + mIsLockoutTimerStarted = false; + + if (mIsLockoutTimerAborted) { + mIsLockoutTimerAborted = false; + return; + } + + // if more failures seen since the timer started, need to restart timer again + auto deltaTime = getLockoutTimeLeft(); + if (deltaTime <= 0) { + cb->onLockoutCleared(); + reset(true); + } else { + startLockoutTimer(deltaTime, cb); + } +} + +void FakeLockoutTracker::abortTimer() { + if (mIsLockoutTimerStarted) mIsLockoutTimerAborted = true; +} + +} // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/FakeLockoutTracker.h b/biometrics/face/aidl/default/FakeLockoutTracker.h new file mode 100644 index 0000000000..f2d38f3608 --- /dev/null +++ b/biometrics/face/aidl/default/FakeLockoutTracker.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 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. + */ + +#pragma once + +#include +#include +#include +#include + +namespace aidl::android::hardware::biometrics::face { + +// Lockout implementation for Face Virtual HAL +class FakeLockoutTracker { + public: + FakeLockoutTracker() + : mFailedCount(0), + mLastFailedTime(0), + mIsLockoutTimerStarted(false), + mIsLockoutTimerAborted(false) {} + ~FakeLockoutTracker() {} + + enum class LockoutMode : int8_t { kNone = 0, kTimed, kPermanent }; + + bool checkIfLockout(ISessionCallback*); + void addFailedAttempt(ISessionCallback*); + int64_t getLockoutTimeLeft(); + LockoutMode getMode(); + void reset(bool dueToTimerExpire = false); + inline std::string toString() const { + std::ostringstream os; + os << "----- FakeLockoutTracker:: -----" << std::endl; + os << "mFailedCount:" << mFailedCount; + os << ", mCurrentMode:" << (int)mCurrentMode; + os << ", mLastFailedTime:" << (int)(mLastFailedTime / 1000000LL); + os << ", mIsLockoutTimerStarted:" << mIsLockoutTimerStarted; + os << ", mIsLockoutTimerAborted:" << mIsLockoutTimerAborted; + os << std::endl; + return os.str(); + } + + private: + void startLockoutTimer(int64_t timeout, ISessionCallback* cb); + void lockoutTimerExpired(ISessionCallback* cb); + int32_t getTimedLockoutDuration(); + void abortTimer(); + + private: + int32_t mFailedCount; + int32_t mTimedFailedCount; + int64_t mLastFailedTime; + LockoutMode mCurrentMode; + bool mIsLockoutTimerStarted; + bool mIsLockoutTimerAborted; +}; + +} // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/README.md b/biometrics/face/aidl/default/README.md index 922525836d..c9a8cfeeaa 100644 --- a/biometrics/face/aidl/default/README.md +++ b/biometrics/face/aidl/default/README.md @@ -51,31 +51,31 @@ $ adb shell cmd face syncadb shell cmd face sync To authenticate successfully, the captured (hit) must match the enrollment id
set above. To trigger authentication failure, set the hit id to a different value. -```shell +`shell $ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800 -$ adb shell setprop vendor.face.virtual.enrollment_hit 1 -``` +$ adb shell setprop vendor.face.virtual.enrollment_hit 1` ### AcquiredInfo + AcquiredInfo codes can be sent during authentication by specifying the sysprop.
The codes is sent in sequence and in the interval of operation_authentication_duration/numberOfAcquiredInfoCode -```shell -$ adb shell setprop vendor.face.virtual.operation_authenticate_acquired 6,9,1013 -``` +`shell +$ adb shell setprop vendor.face.virtual.operation_authenticate_acquired 6,9,1013` Refer to [AcquiredInfo.aidl](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/biometrics/face/aidl/android/hardware/biometrics/face/AcquiredInfo.aidl) for full face acquiredInfo codes. Note: For vendor specific acquired info, acquiredInfo = 1000 + vendorCode. ### Error Insertion -Error can be inserted during authentction by specifying the authenticate_error sysprop. -```shell -$ adb shell setprop vendor.face.virtual.operation_authenticate_error 4 -``` -Refer to [Error.aidl](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl) for full face error codes +Error can be inserted during authentction by specifying the authenticate_error +sysprop. `shell $ adb shell setprop +vendor.face.virtual.operation_authenticate_error 4` Refer to +[Error.aidl](https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/biometrics/face/aidl/android/hardware/biometrics/face/Error.aidl) +for full face error codes ## Enrollment via Settings -Enrollment process is specified by sysprop `next_enrollment` in the following format +Enrollment process is specified by sysprop `next_enrollment` in the following +format ```shell Format: : @@ -88,7 +88,40 @@ Format: : E.g. $ adb shell setprop vendor.face.virtual.next_enrollment 1:6000-[21,8,1,1108,1,10,1113,1,1118,1124]:true ``` + If next_enrollment prop is not set, the following default value is used:
  defaultNextEnrollment="1:1000-[21,7,1,1103],1500-[1108,1],2000-[1113,1],2500-[1118,1]:true"
Note: Enrollment data and configuration can be supported upon request in case of needs +## Lockout + +Device lockout is based on the number of consecutive failed authentication attempts. There are a few +flavors of lockout mechanisms that are supported by virtula HAL
+ +### Permanent Lockout + +There are two sysprop to control permanent lockout
+1. general lockout feature enable
+2. threshold of failed attempts
+`shell +$ adb shell setprop persist.vendor.face.virtual.lockout_enable true +$ adb shell setprop persist.vendor.face.virtual.lockout_permanent_threshold 3` + +### Temporary Lockout + +There are a few parameters to control temporary lockout (aka timed lockout):
+1. enable lockout (general lockout feature enable, and timed lcokout enable)
+2. threshold of failed attempts
+3. timeout in ms
+`shell +$ adb shell setprop persist.vendor.face.virtual.lockout_enable true +$ adb shell setprop persist.vendor.face.virtual.lockout_timed_enable true +$ adb shell setprop persist.vendor.face.virtual.lockout_timed_threshold 5 +$ adb shell setprop persist.vendor.face.virtual.lockout_timed_duration 10000` + +### Forced Lockout + +A permanent lockout can be inserted on next authentication attempt independent of the failed
+attempt count. This is a feature purely for test purpose. +`shell +$ adb shell setprop persist.vendor.face.virtual.lockout true` diff --git a/biometrics/face/aidl/default/face.sysprop b/biometrics/face/aidl/default/face.sysprop index be320151af..95b0b43ca7 100644 --- a/biometrics/face/aidl/default/face.sysprop +++ b/biometrics/face/aidl/default/face.sysprop @@ -92,7 +92,7 @@ prop { api_name: "challenge" } -# if locked out +# if forced to lock out (Default to false) prop { prop_name: "vendor.face.virtual.lockout" type: Boolean @@ -176,3 +176,47 @@ prop { api_name: "operation_authenticate_acquired" } +# whether support lockout based on the failed auth attempts (default: false) +prop { + prop_name: "persist.vendor.face.virtual.lockout_enable" + type: Boolean + scope: Internal + access: ReadWrite + api_name: "lockout_enable" +} + +# whether support timed_lockout based on the failed auth attempts (default: false) +prop { + prop_name: "persist.vendor.face.virtual.lockout_timed_enable" + type: Boolean + scope: Internal + access: ReadWrite + api_name: "lockout_timed_enable" +} + +# temperory lockout threshold in number of consecutive failed auth attempts +prop { + prop_name: "persist.vendor.face.virtual.lockout_timed_threshold" + type: Integer + scope: Internal + access: ReadWrite + api_name: "lockout_timed_threshold" +} + +# temporary lockout duration in ms (default: 10000ms) +prop { + prop_name: "persist.vendor.face.virtual.lockout_timed_duration" + type: Integer + scope: Internal + access: ReadWrite + api_name: "lockout_timed_duration" +} + +# permanently lockout threshold in number of consecutive failed auth attempts +prop { + prop_name: "persist.vendor.face.virtual.lockout_permanent_threshold" + type: Integer + scope: Internal + access: ReadWrite + api_name: "lockout_permanent_threshold" +} diff --git a/biometrics/face/aidl/default/tests/FakeLockoutTrackerTest.cpp b/biometrics/face/aidl/default/tests/FakeLockoutTrackerTest.cpp new file mode 100644 index 0000000000..fa07d1dfeb --- /dev/null +++ b/biometrics/face/aidl/default/tests/FakeLockoutTrackerTest.cpp @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2023 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. + */ + +#include +#include +#include +#include + +#include + +#include "FakeLockoutTracker.h" +#include "util/Util.h" + +using namespace ::android::face::virt; +using namespace ::aidl::android::hardware::biometrics::face; + +namespace aidl::android::hardware::biometrics::face { + +class TestSessionCallback : public BnSessionCallback { + public: + ndk::ScopedAStatus onChallengeGenerated(int64_t /*challenge*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onChallengeRevoked(int64_t /*challenge*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onError(face::Error, int32_t /*vendorCode*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onEnrollmentProgress(int32_t /*enrollmentId*/, + int32_t /*remaining*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t /*enrollmentId*/, + const keymaster::HardwareAuthToken&) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onAuthenticationFailed() override { return ndk::ScopedAStatus::ok(); }; + ::ndk::ScopedAStatus onInteractionDetected() override { return ndk::ScopedAStatus::ok(); }; + ::ndk::ScopedAStatus onEnrollmentsEnumerated(const std::vector&) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onEnrollmentsRemoved( + const std::vector& /*enrollmentIds*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t /*authenticatorId*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t /*authenticatorId*/) override { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onEnrollmentFrame(const EnrollmentFrame&) override { + return ndk::ScopedAStatus::ok(); + } + ::ndk::ScopedAStatus onFeaturesRetrieved(const std::vector&) { + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onFeatureSet(Feature) override { return ndk::ScopedAStatus::ok(); } + ::ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); } + ::ndk::ScopedAStatus onAuthenticationFrame(const AuthenticationFrame&) override { + return ndk::ScopedAStatus::ok(); + } + + ndk::ScopedAStatus onLockoutTimed(int64_t timeLeft) override { + mLockoutTimed++; + mTimeLeft = timeLeft; + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onLockoutPermanent() override { + mLockoutPermanent++; + return ndk::ScopedAStatus::ok(); + }; + ::ndk::ScopedAStatus onLockoutCleared() override { + mTimeLeft = 0; + mLockoutTimed = 0; + mLockoutPermanent = 0; + return ndk::ScopedAStatus::ok(); + }; + + int64_t mTimeLeft = 0; + int mLockoutTimed = 0; + int mLockoutPermanent = 0; +}; + +class FakeLockoutTrackerTest : public ::testing::Test { + protected: + static constexpr int32_t LOCKOUT_TIMED_THRESHOLD = 3; + static constexpr int32_t LOCKOUT_PERMANENT_THRESHOLD = 5; + static constexpr int32_t LOCKOUT_TIMED_DURATION = 100; + + void SetUp() override { + FaceHalProperties::lockout_timed_threshold(LOCKOUT_TIMED_THRESHOLD); + FaceHalProperties::lockout_timed_duration(LOCKOUT_TIMED_DURATION); + FaceHalProperties::lockout_permanent_threshold(LOCKOUT_PERMANENT_THRESHOLD); + mCallback = ndk::SharedRefBase::make(); + } + + void TearDown() override { + // reset to default + FaceHalProperties::lockout_timed_threshold(5); + FaceHalProperties::lockout_timed_duration(20); + FaceHalProperties::lockout_permanent_threshold(10000); + FaceHalProperties::lockout_enable(false); + FaceHalProperties::lockout(false); + } + + FakeLockoutTracker mLockoutTracker; + std::shared_ptr mCallback; +}; + +TEST_F(FakeLockoutTrackerTest, addFailedAttemptDisable) { + FaceHalProperties::lockout_enable(false); + for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD + 1; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone); + ASSERT_EQ(0, mCallback->mLockoutTimed); +} + +TEST_F(FakeLockoutTrackerTest, addFailedAttemptPermanent) { + FaceHalProperties::lockout_enable(true); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); + for (int i = 0; i < LOCKOUT_PERMANENT_THRESHOLD - 1; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_NE(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent); + ASSERT_EQ(0, mCallback->mLockoutPermanent); + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent); + ASSERT_EQ(1, mCallback->mLockoutPermanent); + ASSERT_TRUE(mLockoutTracker.checkIfLockout(mCallback.get())); + ASSERT_EQ(2, mCallback->mLockoutPermanent); +} + +TEST_F(FakeLockoutTrackerTest, addFailedAttemptLockoutTimed) { + FaceHalProperties::lockout_enable(true); + FaceHalProperties::lockout_timed_enable(true); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); + for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kTimed); + ASSERT_EQ(1, mCallback->mLockoutTimed); + ASSERT_TRUE(mLockoutTracker.checkIfLockout(mCallback.get())); + ASSERT_EQ(2, mCallback->mLockoutTimed); + // time left + int N = 5; + int64_t prevTimeLeft = INT_MAX; + for (int i = 0; i < N; i++) { + SLEEP_MS(LOCKOUT_TIMED_DURATION / N + 1); + int64_t currTimeLeft = mLockoutTracker.getLockoutTimeLeft(); + ASSERT_TRUE(currTimeLeft < prevTimeLeft); + prevTimeLeft = currTimeLeft; + } + SLEEP_MS(LOCKOUT_TIMED_DURATION / N); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone); +} + +TEST_F(FakeLockoutTrackerTest, addFailedAttemptLockout_TimedThenPermanent) { + FaceHalProperties::lockout_enable(true); + FaceHalProperties::lockout_timed_enable(true); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); + for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kTimed); + SLEEP_MS(LOCKOUT_TIMED_DURATION + 20); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone); + for (int i = 0; i < LOCKOUT_PERMANENT_THRESHOLD - LOCKOUT_TIMED_THRESHOLD; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent); +} + +TEST_F(FakeLockoutTrackerTest, addFailedAttemptLockoutTimedTwice) { + FaceHalProperties::lockout_enable(true); + FaceHalProperties::lockout_timed_enable(true); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); + ASSERT_EQ(0, mCallback->mLockoutTimed); + for (int i = 0; i < LOCKOUT_TIMED_THRESHOLD; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + SLEEP_MS(LOCKOUT_TIMED_DURATION / 2); + mLockoutTracker.addFailedAttempt(mCallback.get()); + SLEEP_MS(LOCKOUT_TIMED_DURATION); + ASSERT_EQ(2, mCallback->mLockoutTimed); + ASSERT_TRUE(mLockoutTracker.checkIfLockout(mCallback.get())); + SLEEP_MS(LOCKOUT_TIMED_DURATION); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); +} + +TEST_F(FakeLockoutTrackerTest, resetLockout) { + FaceHalProperties::lockout_enable(true); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kNone); + for (int i = 0; i < LOCKOUT_PERMANENT_THRESHOLD; i++) + mLockoutTracker.addFailedAttempt(mCallback.get()); + ASSERT_EQ(mLockoutTracker.getMode(), FakeLockoutTracker::LockoutMode::kPermanent); + mLockoutTracker.reset(); + ASSERT_FALSE(mLockoutTracker.checkIfLockout(mCallback.get())); +} + +} // namespace aidl::android::hardware::biometrics::face + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + ABinderProcess_startThreadPool(); + return RUN_ALL_TESTS(); +}