diff --git a/biometrics/common/util/include/util/Util.h b/biometrics/common/util/include/util/Util.h index da19dc6afe..efd66bc373 100644 --- a/biometrics/common/util/include/util/Util.h +++ b/biometrics/common/util/include/util/Util.h @@ -23,6 +23,9 @@ #include #include +#include +using ::android::base::ParseInt; + namespace aidl::android::hardware::biometrics { #define SLEEP_MS(x) \ @@ -64,6 +67,87 @@ class Util { std::sregex_token_iterator()); return parts; } + + // Returns a vector of integers for the string separated by comma, + // Empty vector is returned if there is any parsing error + static std::vector parseIntSequence(const std::string& str, + const std::string& sep = ",") { + std::vector seqs = Util::split(str, sep); + std::vector res; + + for (const auto& seq : seqs) { + int32_t val; + if (ParseInt(seq, &val)) { + res.push_back(val); + } else { + LOG(WARNING) << "Invalid int sequence:" + str + " seq:" + seq; + res.clear(); + break; + } + } + + return res; + } + + // Parses a single enrollment stage string in the format of + // enroll_stage_spec: [-acquiredInfos] + // duration: integerInMs + // acquiredInfos: [info1,info2,...] + // + // Returns false if there is parsing error + // + static bool parseEnrollmentCaptureSingle(const std::string& str, + std::vector>& res) { + std::vector defaultAcquiredInfo = {1}; + bool aborted = true; + + do { + std::smatch sms; + // Parses strings like "1000-[5,1]" or "500" + std::regex ex("((\\d+)(-\\[([\\d|,]+)\\])?)"); + if (!regex_match(str.cbegin(), str.cend(), sms, ex)) break; + int32_t duration; + if (!ParseInt(sms.str(2), &duration)) break; + res.push_back({duration}); + if (!sms.str(4).empty()) { + auto acqv = parseIntSequence(sms.str(4)); + if (acqv.empty()) break; + res.push_back(acqv); + } else + res.push_back(defaultAcquiredInfo); + aborted = false; + } while (0); + + return !aborted; + } + + // Parses enrollment string consisting of one or more stages in the formst of + // [,enroll_stage_spec,...] + // Empty vector is returned in case of parsing error + static std::vector> parseEnrollmentCapture(const std::string& str) { + std::vector> res; + + std::string s(str); + s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end()); + bool aborted = false; + std::smatch sms; + // Parses strings like "1000-[5,1],500,800-[6,5,1]" + // -------------- ----- --------------- + // into parts: A B C + while (regex_search(s, sms, std::regex("^(,)?(\\d+(-\\[[\\d|,]+\\])?)"))) { + if (!parseEnrollmentCaptureSingle(sms.str(2), res)) { + aborted = true; + break; + } + s = sms.suffix(); + } + if (aborted || s.length() != 0) { + res.clear(); + LOG(ERROR) << "Failed to parse enrollment captures:" + str; + } + + return res; + } }; } // namespace aidl::android::hardware::biometrics diff --git a/biometrics/face/aidl/default/FakeFaceEngine.cpp b/biometrics/face/aidl/default/FakeFaceEngine.cpp index 0f088f4331..578231d1ca 100644 --- a/biometrics/face/aidl/default/FakeFaceEngine.cpp +++ b/biometrics/face/aidl/default/FakeFaceEngine.cpp @@ -136,56 +136,127 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI const std::future& cancel) { BEGIN_OP(FaceHalProperties::operation_authenticate_latency().value_or(0)); - // Signal to the framework that we have begun authenticating. - AuthenticationFrame frame; - frame.data.acquiredInfo = AcquiredInfo::START; - frame.data.vendorCode = 0; - cb->onAuthenticationFrame(frame); - - // Also signal that we have opened the camera. - frame = {}; - frame.data.acquiredInfo = AcquiredInfo::FIRST_FRAME_RECEIVED; - frame.data.vendorCode = 0; - cb->onAuthenticationFrame(frame); - - auto now = Util::getSystemNanoTime(); - int64_t duration = FaceHalProperties::operation_authenticate_duration().value_or(0); - if (duration > 0) { - do { - SLEEP_MS(5); - } while (!Util::hasElapsed(now, duration)); - } - - if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { - LOG(ERROR) << "Fail: operation_authenticate_fails"; - cb->onError(Error::VENDOR, 0 /* vendorError */); - return; - } - - if (FaceHalProperties::lockout().value_or(false)) { - LOG(ERROR) << "Fail: lockout"; - cb->onLockoutPermanent(); - cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */); - return; - } - - if (shouldCancel(cancel)) { - LOG(ERROR) << "Fail: cancel"; - cb->onError(Error::CANCELED, 0 /* vendorCode */); - return; - } - auto id = FaceHalProperties::enrollment_hit().value_or(0); auto enrolls = FaceHalProperties::enrollments(); auto isEnrolled = std::find(enrolls.begin(), enrolls.end(), id) != enrolls.end(); - if (id < 0 || !isEnrolled) { - LOG(ERROR) << (isEnrolled ? "invalid enrollment hit" : "Fail: not enrolled"); - cb->onAuthenticationFailed(); + + auto vec2str = [](std::vector va) { + std::stringstream ss; + bool isFirst = true; + for (auto ac : va) { + if (!isFirst) ss << ","; + ss << std::to_string((int8_t)ac); + isFirst = false; + } + return ss.str(); + }; + + // default behavior mimic face sensor in U + int64_t defaultAuthDuration = 500; + std::string defaultAcquiredInfo = + vec2str({AcquiredInfo::START, AcquiredInfo::FIRST_FRAME_RECEIVED}); + if (!isEnrolled) { + std::vector v; + for (int i = 0; i < 56; i++) v.push_back(AcquiredInfo::NOT_DETECTED); + defaultAcquiredInfo += "," + vec2str(v); + defaultAuthDuration = 2100; + } else { + defaultAcquiredInfo += "," + vec2str({AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, + AcquiredInfo::TOO_BRIGHT, AcquiredInfo::TOO_BRIGHT, + AcquiredInfo::GOOD, AcquiredInfo::GOOD}); + } + + int64_t now = Util::getSystemNanoTime(); + int64_t duration = + FaceHalProperties::operation_authenticate_duration().value_or(defaultAuthDuration); + auto acquired = + FaceHalProperties::operation_authenticate_acquired().value_or(defaultAcquiredInfo); + auto acquiredInfos = Util::parseIntSequence(acquired); + int N = acquiredInfos.size(); + + if (N == 0) { + LOG(ERROR) << "Fail to parse authentiate acquired info: " + acquired; cb->onError(Error::UNABLE_TO_PROCESS, 0 /* vendorError */); return; } - cb->onAuthenticationSucceeded(id, {} /* hat */); + int i = 0; + do { + if (FaceHalProperties::lockout().value_or(false)) { + LOG(ERROR) << "Fail: lockout"; + cb->onLockoutPermanent(); + cb->onError(Error::HW_UNAVAILABLE, 0 /* vendorError */); + return; + } + + if (FaceHalProperties::operation_authenticate_fails().value_or(false)) { + LOG(ERROR) << "Fail: operation_authenticate_fails"; + cb->onAuthenticationFailed(); + return; + } + + auto err = FaceHalProperties::operation_authenticate_error().value_or(0); + if (err != 0) { + LOG(ERROR) << "Fail: operation_authenticate_error"; + auto ec = convertError(err); + cb->onError(ec.first, ec.second); + return; /* simply terminating current operation for any user inserted error, + revisit if tests need*/ + } + + if (shouldCancel(cancel)) { + LOG(ERROR) << "Fail: cancel"; + cb->onError(Error::CANCELED, 0 /* vendorCode */); + return; + } + + if (i < N) { + auto ac = convertAcquiredInfo(acquiredInfos[i]); + AuthenticationFrame frame; + frame.data.acquiredInfo = ac.first; + frame.data.vendorCode = ac.second; + cb->onAuthenticationFrame(frame); + LOG(INFO) << "AcquiredInfo:" << i << ": (" << (int)ac.first << "," << (int)ac.second + << ")"; + i++; + } + + SLEEP_MS(duration / N); + } while (!Util::hasElapsed(now, duration)); + + if (id > 0 && isEnrolled) { + cb->onAuthenticationSucceeded(id, {} /* hat */); + return; + } else { + LOG(ERROR) << "Fail: face not enrolled"; + cb->onAuthenticationFailed(); + cb->onError(Error::TIMEOUT, 0 /* vendorError*/); + return; + } +} + +std::pair FakeFaceEngine::convertAcquiredInfo(int32_t code) { + std::pair res; + if (code > FACE_ACQUIRED_VENDOR_BASE) { + res.first = AcquiredInfo::VENDOR; + res.second = code - FACE_ACQUIRED_VENDOR_BASE; + } else { + res.first = (AcquiredInfo)code; + res.second = 0; + } + return res; +} + +std::pair FakeFaceEngine::convertError(int32_t code) { + std::pair res; + if (code > FACE_ERROR_VENDOR_BASE) { + res.first = Error::VENDOR; + res.second = code - FACE_ERROR_VENDOR_BASE; + } else { + res.first = (Error)code; + res.second = 0; + } + return res; } void FakeFaceEngine::detectInteractionImpl(ISessionCallback* cb, const std::future& cancel) { @@ -315,4 +386,4 @@ void FakeFaceEngine::resetLockoutImpl(ISessionCallback* cb, cb->onLockoutCleared(); } -} // namespace aidl::android::hardware::biometrics::face \ No newline at end of file +} // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/FakeFaceEngine.h b/biometrics/face/aidl/default/FakeFaceEngine.h index eb9944172f..06dd396c70 100644 --- a/biometrics/face/aidl/default/FakeFaceEngine.h +++ b/biometrics/face/aidl/default/FakeFaceEngine.h @@ -62,6 +62,12 @@ class FakeFaceEngine { void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/); std::mt19937 mRandom; + + private: + static constexpr int32_t FACE_ACQUIRED_VENDOR_BASE = 1000; + static constexpr int32_t FACE_ERROR_VENDOR_BASE = 1000; + std::pair convertAcquiredInfo(int32_t code); + std::pair convertError(int32_t code); }; } // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/face/aidl/default/README.md b/biometrics/face/aidl/default/README.md index 77bfe57209..516a7aaea7 100644 --- a/biometrics/face/aidl/default/README.md +++ b/biometrics/face/aidl/default/README.md @@ -18,6 +18,7 @@ On pixel devicse (non-CF), by default (after manufacture reset), Face VHAL is no Face VHAL enabling is gated by the following two AND conditions: 1. The Face VHAL feature flag (as part of Trunk-development strategy) must be tured until the flags life-cycle ends. 2. The Face VHAL must be enabled via sysprop +See adb commands below ##Getting Stared @@ -47,8 +48,7 @@ authentication failure, set the hit id to a different value. $ adb shell setprop vendor.face.virtual.operation_authenticate_duration 800 $ adb shell setprop vendor.face.virtual.enrollment_hit 1 ``` -Note: At the initial phase of the Face VHAL development, Face-on-camera simulation is not supported, hence -the authentication immediately occurrs as soon as the authentication request arriving at VHAL. +Refer to face.sysprop for full supported features of authentication, such as error and acquiredInfo insertion ## Enrollment via Setup diff --git a/biometrics/face/aidl/default/face.sysprop b/biometrics/face/aidl/default/face.sysprop index 6b0f37fec5..be320151af 100644 --- a/biometrics/face/aidl/default/face.sysprop +++ b/biometrics/face/aidl/default/face.sysprop @@ -157,3 +157,22 @@ prop { access: ReadWrite api_name: "operation_authenticate_duration" } + +# insert error for authenticate operations +prop { + prop_name: "vendor.face.virtual.operation_authenticate_error" + type: Integer + scope: Internal + access: ReadWrite + api_name: "operation_authenticate_error" +} + +# acquired info during authentication in format of sequence +prop { + prop_name: "vendor.face.virtual.operation_authenticate_acquired" + type: String + scope: Internal + access: ReadWrite + api_name: "operation_authenticate_acquired" +} + diff --git a/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp index c8ad6b7dc3..6897dc4aba 100644 --- a/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp +++ b/biometrics/face/aidl/default/tests/FakeFaceEngineTest.cpp @@ -245,7 +245,7 @@ TEST_F(FakeFaceEngineTest, AuthenticateFailedForUnEnrolled) { FaceHalProperties::enrollments({3}); FaceHalProperties::enrollment_hit(100); mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future()); - ASSERT_EQ(Error::UNABLE_TO_PROCESS, mCallback->mError); + ASSERT_EQ(Error::TIMEOUT, mCallback->mError); ASSERT_TRUE(mCallback->mAuthenticateFailed); } @@ -380,4 +380,4 @@ TEST_F(FakeFaceEngineTest, ResetLockoutWithAuth) { ASSERT_FALSE(mCallback->mAuthenticateFailed); } -} // namespace aidl::android::hardware::biometrics::face \ No newline at end of file +} // namespace aidl::android::hardware::biometrics::face diff --git a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp index 54076c88e3..574570e961 100644 --- a/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp +++ b/biometrics/fingerprint/aidl/default/FakeFingerprintEngine.cpp @@ -142,7 +142,7 @@ bool FakeFingerprintEngine::onEnrollFingerDown(ISessionCallback* cb, return true; } auto enrollmentId = std::stoi(parts[0]); - auto progress = parseEnrollmentCapture(parts[1]); + auto progress = Util::parseEnrollmentCapture(parts[1]); for (size_t i = 0; i < progress.size(); i += 2) { auto left = (progress.size() - i) / 2 - 1; auto duration = progress[i][0]; @@ -193,7 +193,7 @@ bool FakeFingerprintEngine::onAuthenticateFingerDown(ISessionCallback* cb, int64_t now = Util::getSystemNanoTime(); int64_t duration = FingerprintHalProperties::operation_authenticate_duration().value_or(10); auto acquired = FingerprintHalProperties::operation_authenticate_acquired().value_or("1"); - auto acquiredInfos = parseIntSequence(acquired); + auto acquiredInfos = Util::parseIntSequence(acquired); int N = acquiredInfos.size(); if (N == 0) { @@ -271,7 +271,7 @@ bool FakeFingerprintEngine::onDetectInteractFingerDown(ISessionCallback* cb, FingerprintHalProperties::operation_detect_interaction_duration().value_or(10); auto acquired = FingerprintHalProperties::operation_detect_interaction_acquired().value_or("1"); - auto acquiredInfos = parseIntSequence(acquired); + auto acquiredInfos = Util::parseIntSequence(acquired); int N = acquiredInfos.size(); int64_t now = Util::getSystemNanoTime(); @@ -450,76 +450,6 @@ SensorLocation FakeFingerprintEngine::defaultSensorLocation() { return SensorLocation(); } -std::vector FakeFingerprintEngine::parseIntSequence(const std::string& str, - const std::string& sep) { - std::vector seqs = Util::split(str, sep); - std::vector res; - - for (const auto& seq : seqs) { - int32_t val; - if (ParseInt(seq, &val)) { - res.push_back(val); - } else { - LOG(WARNING) << "Invalid int sequence:" + str; - res.clear(); - break; - } - } - - return res; -} - -bool FakeFingerprintEngine::parseEnrollmentCaptureSingle(const std::string& str, - std::vector>& res) { - std::vector defaultAcquiredInfo = {(int32_t)AcquiredInfo::GOOD}; - bool aborted = true; - - do { - std::smatch sms; - // Parses strings like "1000-[5,1]" or "500" - std::regex ex("((\\d+)(-\\[([\\d|,]+)\\])?)"); - if (!regex_match(str.cbegin(), str.cend(), sms, ex)) break; - int32_t duration; - if (!ParseInt(sms.str(2), &duration)) break; - res.push_back({duration}); - if (!sms.str(4).empty()) { - auto acqv = parseIntSequence(sms.str(4)); - if (acqv.empty()) break; - res.push_back(acqv); - } else - res.push_back(defaultAcquiredInfo); - aborted = false; - } while (0); - - return !aborted; -} - -std::vector> FakeFingerprintEngine::parseEnrollmentCapture( - const std::string& str) { - std::vector> res; - - std::string s(str); - s.erase(std::remove_if(s.begin(), s.end(), ::isspace), s.end()); - bool aborted = false; - std::smatch sms; - // Parses strings like "1000-[5,1],500,800-[6,5,1]" - // ---------- --- ----------- - // into parts: A B C - while (regex_search(s, sms, std::regex("^(,)?(\\d+(-\\[[\\d|,]+\\])?)"))) { - if (!parseEnrollmentCaptureSingle(sms.str(2), res)) { - aborted = true; - break; - } - s = sms.suffix(); - } - if (aborted || s.length() != 0) { - res.clear(); - LOG(ERROR) << "Failed to parse enrollment captures:" + str; - } - - return res; -} - std::pair FakeFingerprintEngine::convertAcquiredInfo(int32_t code) { std::pair res; if (code > FINGERPRINT_ACQUIRED_VENDOR_BASE) { diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h index 2450115aaf..15d83604d5 100644 --- a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h +++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h @@ -68,10 +68,6 @@ class FakeFingerprintEngine { virtual void fingerDownAction(); - std::vector parseIntSequence(const std::string& str, const std::string& sep = ","); - - std::vector> parseEnrollmentCapture(const std::string& str); - int32_t getLatency(const std::vector>& latencyVec); std::mt19937 mRandom; @@ -110,8 +106,6 @@ class FakeFingerprintEngine { static constexpr int32_t FINGERPRINT_ERROR_VENDOR_BASE = 1000; std::pair convertAcquiredInfo(int32_t code); std::pair convertError(int32_t code); - bool parseEnrollmentCaptureSingle(const std::string& str, - std::vector>& res); int32_t getRandomInRange(int32_t bound1, int32_t bound2); bool checkSensorLockout(ISessionCallback*); void clearLockout(ISessionCallback* cb); diff --git a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp index fe405f4110..45b5be8d2e 100644 --- a/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp +++ b/biometrics/fingerprint/aidl/default/tests/FakeFingerprintEngineTest.cpp @@ -421,29 +421,29 @@ TEST_F(FakeFingerprintEngineTest, RemoveEnrolled) { TEST_F(FakeFingerprintEngineTest, parseIntSequence) { std::vector seqV; - seqV = mEngine.parseIntSequence(""); + seqV = Util::parseIntSequence(""); ASSERT_EQ(0, seqV.size()); - seqV = mEngine.parseIntSequence("2"); + seqV = Util::parseIntSequence("2"); ASSERT_EQ(1, seqV.size()); ASSERT_EQ(2, seqV[0]); - seqV = mEngine.parseIntSequence("2,3,4"); + seqV = Util::parseIntSequence("2,3,4"); std::vector expV{2, 3, 4}; ASSERT_EQ(expV, seqV); - seqV = mEngine.parseIntSequence("2,3,a"); + seqV = Util::parseIntSequence("2,3,a"); ASSERT_EQ(0, seqV.size()); - seqV = mEngine.parseIntSequence("2, 3, 4"); + seqV = Util::parseIntSequence("2, 3, 4"); ASSERT_EQ(expV, seqV); - seqV = mEngine.parseIntSequence("123,456"); + seqV = Util::parseIntSequence("123,456"); ASSERT_EQ(2, seqV.size()); std::vector expV1{123, 456}; ASSERT_EQ(expV1, seqV); - seqV = mEngine.parseIntSequence("12f3,456"); + seqV = Util::parseIntSequence("12f3,456"); ASSERT_EQ(0, seqV.size()); } TEST_F(FakeFingerprintEngineTest, parseEnrollmentCaptureOk) { std::vector> ecV; - ecV = mEngine.parseEnrollmentCapture("100,200,300"); + ecV = Util::parseEnrollmentCapture("100,200,300"); ASSERT_EQ(6, ecV.size()); std::vector> expE{{100}, {200}, {300}}; std::vector defC{1}; @@ -451,26 +451,26 @@ TEST_F(FakeFingerprintEngineTest, parseEnrollmentCaptureOk) { ASSERT_EQ(expE[i / 2], ecV[i]); ASSERT_EQ(defC, ecV[i + 1]); } - ecV = mEngine.parseEnrollmentCapture("100"); + ecV = Util::parseEnrollmentCapture("100"); ASSERT_EQ(2, ecV.size()); ASSERT_EQ(expE[0], ecV[0]); ASSERT_EQ(defC, ecV[1]); - ecV = mEngine.parseEnrollmentCapture("100-[5,6,7]"); + ecV = Util::parseEnrollmentCapture("100-[5,6,7]"); std::vector expC{5, 6, 7}; ASSERT_EQ(2, ecV.size()); for (int i = 0; i < ecV.size(); i += 2) { ASSERT_EQ(expE[i / 2], ecV[i]); ASSERT_EQ(expC, ecV[i + 1]); } - ecV = mEngine.parseEnrollmentCapture("100-[5,6,7], 200, 300-[9,10]"); + ecV = Util::parseEnrollmentCapture("100-[5,6,7], 200, 300-[9,10]"); std::vector> expC1{{5, 6, 7}, {1}, {9, 10}}; ASSERT_EQ(6, ecV.size()); for (int i = 0; i < ecV.size(); i += 2) { ASSERT_EQ(expE[i / 2], ecV[i]); ASSERT_EQ(expC1[i / 2], ecV[i + 1]); } - ecV = mEngine.parseEnrollmentCapture("100-[5,6,7], 200-[2,1], 300-[9]"); + ecV = Util::parseEnrollmentCapture("100-[5,6,7], 200-[2,1], 300-[9]"); std::vector> expC2{{5, 6, 7}, {2, 1}, {9}}; ASSERT_EQ(ecV.size(), 6); for (int i = 0; i < ecV.size(); i += 2) { @@ -484,7 +484,7 @@ TEST_F(FakeFingerprintEngineTest, parseEnrollmentCaptureFail) { "100,2x0,300", "200-[f]", "a,b"}; std::vector> ecV; for (const auto& s : badStr) { - ecV = mEngine.parseEnrollmentCapture(s); + ecV = Util::parseEnrollmentCapture(s); ASSERT_EQ(ecV.size(), 0); } } @@ -508,7 +508,7 @@ TEST_F(FakeFingerprintEngineTest, randomLatency) { TEST_F(FakeFingerprintEngineTest, lockoutTimer) { mEngine.startLockoutTimer(200, mCallback.get()); ASSERT_TRUE(mEngine.getLockoutTimerStarted()); - std::this_thread::sleep_for(std::chrono::milliseconds(210)); + std::this_thread::sleep_for(std::chrono::milliseconds(230)); ASSERT_FALSE(mEngine.getLockoutTimerStarted()); ASSERT_TRUE(mCallback->mLockoutCleared); }