Face Biometric Virtual HAL Authentication Implementation

Bug: 294254230
Test: atest VtsHalBiometricsFaceTargetTest
Test: atest VtsHalBiometricsFingerprintTargetTest
Test: atest android.hardware.biometrics.face.FakeFaceEngineTest
Test: atest android.hardware.biometrics.fingerprint.FakeFingerprintEngineTest
Change-Id: Id7a4698730307235c5de94811f5e396ff4b9c5ae
This commit is contained in:
Jeff Pu
2023-09-25 15:11:19 +00:00
parent bd3882ea1f
commit 484d2e70c3
9 changed files with 245 additions and 141 deletions

View File

@@ -23,6 +23,9 @@
#include <thread>
#include <vector>
#include <android-base/parseint.h>
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<int32_t> parseIntSequence(const std::string& str,
const std::string& sep = ",") {
std::vector<std::string> seqs = Util::split(str, sep);
std::vector<int32_t> 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: <duration>[-acquiredInfos]
// duration: integerInMs
// acquiredInfos: [info1,info2,...]
//
// Returns false if there is parsing error
//
static bool parseEnrollmentCaptureSingle(const std::string& str,
std::vector<std::vector<int32_t>>& res) {
std::vector<int32_t> 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>[,enroll_stage_spec,...]
// Empty vector is returned in case of parsing error
static std::vector<std::vector<int32_t>> parseEnrollmentCapture(const std::string& str) {
std::vector<std::vector<int32_t>> 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

View File

@@ -136,56 +136,127 @@ void FakeFaceEngine::authenticateImpl(ISessionCallback* cb, int64_t /*operationI
const std::future<void>& 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<AcquiredInfo> 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<AcquiredInfo> 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<AcquiredInfo, int32_t> FakeFaceEngine::convertAcquiredInfo(int32_t code) {
std::pair<AcquiredInfo, int32_t> 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<Error, int32_t> FakeFaceEngine::convertError(int32_t code) {
std::pair<Error, int32_t> 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<void>& cancel) {
@@ -315,4 +386,4 @@ void FakeFaceEngine::resetLockoutImpl(ISessionCallback* cb,
cb->onLockoutCleared();
}
} // namespace aidl::android::hardware::biometrics::face
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -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<AcquiredInfo, int32_t> convertAcquiredInfo(int32_t code);
std::pair<Error, int32_t> convertError(int32_t code);
};
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -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

View File

@@ -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"
}

View File

@@ -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
} // namespace aidl::android::hardware::biometrics::face

View File

@@ -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<int32_t> FakeFingerprintEngine::parseIntSequence(const std::string& str,
const std::string& sep) {
std::vector<std::string> seqs = Util::split(str, sep);
std::vector<int32_t> 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<std::vector<int32_t>>& res) {
std::vector<int32_t> 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<std::vector<int32_t>> FakeFingerprintEngine::parseEnrollmentCapture(
const std::string& str) {
std::vector<std::vector<int32_t>> 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<AcquiredInfo, int32_t> FakeFingerprintEngine::convertAcquiredInfo(int32_t code) {
std::pair<AcquiredInfo, int32_t> res;
if (code > FINGERPRINT_ACQUIRED_VENDOR_BASE) {

View File

@@ -68,10 +68,6 @@ class FakeFingerprintEngine {
virtual void fingerDownAction();
std::vector<int32_t> parseIntSequence(const std::string& str, const std::string& sep = ",");
std::vector<std::vector<int32_t>> parseEnrollmentCapture(const std::string& str);
int32_t getLatency(const std::vector<std::optional<std::int32_t>>& latencyVec);
std::mt19937 mRandom;
@@ -110,8 +106,6 @@ class FakeFingerprintEngine {
static constexpr int32_t FINGERPRINT_ERROR_VENDOR_BASE = 1000;
std::pair<AcquiredInfo, int32_t> convertAcquiredInfo(int32_t code);
std::pair<Error, int32_t> convertError(int32_t code);
bool parseEnrollmentCaptureSingle(const std::string& str,
std::vector<std::vector<int32_t>>& res);
int32_t getRandomInRange(int32_t bound1, int32_t bound2);
bool checkSensorLockout(ISessionCallback*);
void clearLockout(ISessionCallback* cb);

View File

@@ -421,29 +421,29 @@ TEST_F(FakeFingerprintEngineTest, RemoveEnrolled) {
TEST_F(FakeFingerprintEngineTest, parseIntSequence) {
std::vector<int32_t> 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<int32_t> 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<int32_t> 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<std::vector<int32_t>> ecV;
ecV = mEngine.parseEnrollmentCapture("100,200,300");
ecV = Util::parseEnrollmentCapture("100,200,300");
ASSERT_EQ(6, ecV.size());
std::vector<std::vector<int32_t>> expE{{100}, {200}, {300}};
std::vector<int32_t> 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<int32_t> 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<std::vector<int32_t>> 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<std::vector<int32_t>> 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<std::vector<int32_t>> 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);
}