diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp index 24087cf036..6b43bffbdd 100644 --- a/biometrics/fingerprint/aidl/default/Android.bp +++ b/biometrics/fingerprint/aidl/default/Android.bp @@ -6,9 +6,11 @@ cc_binary { vintf_fragments: ["fingerprint-default.xml"], local_include_dirs: ["include"], srcs: [ - "main.cpp", + "CancellationSignal.cpp", "Fingerprint.cpp", "Session.cpp", + "WorkerThread.cpp", + "main.cpp", ], shared_libs: [ "libbase", diff --git a/biometrics/fingerprint/aidl/default/CancellationSignal.cpp b/biometrics/fingerprint/aidl/default/CancellationSignal.cpp new file mode 100644 index 0000000000..659831616d --- /dev/null +++ b/biometrics/fingerprint/aidl/default/CancellationSignal.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 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 "CancellationSignal.h" + +#include +#include + +namespace aidl::android::hardware::biometrics::fingerprint { + +CancellationSignal::CancellationSignal(std::promise&& cancellationPromise) + : mCancellationPromise(std::move(cancellationPromise)) {} + +ndk::ScopedAStatus CancellationSignal::cancel() { + mCancellationPromise.set_value(); + return ndk::ScopedAStatus::ok(); +} + +bool shouldCancel(const std::future& f) { + CHECK(f.valid()); + return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; +} + +} // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/Fingerprint.cpp b/biometrics/fingerprint/aidl/default/Fingerprint.cpp index 6f893468a8..67dc34fdd5 100644 --- a/biometrics/fingerprint/aidl/default/Fingerprint.cpp +++ b/biometrics/fingerprint/aidl/default/Fingerprint.cpp @@ -15,11 +15,12 @@ */ #include "Fingerprint.h" + #include "Session.h" namespace aidl::android::hardware::biometrics::fingerprint { namespace { - +constexpr size_t MAX_WORKER_QUEUE_SIZE = 5; constexpr int SENSOR_ID = 1; constexpr common::SensorStrength SENSOR_STRENGTH = common::SensorStrength::STRONG; constexpr int MAX_ENROLLMENTS_PER_USER = 5; @@ -32,7 +33,8 @@ constexpr char SERIAL_NUMBER[] = "00000001"; } // namespace -Fingerprint::Fingerprint() {} +Fingerprint::Fingerprint() + : mEngine(std::make_unique()), mWorker(MAX_WORKER_QUEUE_SIZE) {} ndk::ScopedAStatus Fingerprint::getSensorProps(std::vector* out) { std::vector hardwareInfos = { @@ -52,14 +54,21 @@ ndk::ScopedAStatus Fingerprint::getSensorProps(std::vector* out) { return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Fingerprint::createSession(int32_t /*sensorId*/, int32_t /*userId*/, +ndk::ScopedAStatus Fingerprint::createSession(int32_t sensorId, int32_t userId, const std::shared_ptr& cb, std::shared_ptr* out) { - *out = SharedRefBase::make(cb); + auto sessionSp = mSession.lock(); + CHECK(sessionSp == nullptr || sessionSp->isClosed()) << "Open session already exists!"; + + auto session = SharedRefBase::make(sensorId, userId, cb, mEngine.get(), &mWorker); + mSession = session; + *out = session; return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Fingerprint::reset() { + // Crash. The system will start a fresh instance of the HAL. + CHECK(false) << "Unable to reset. Crashing."; return ndk::ScopedAStatus::ok(); } diff --git a/biometrics/fingerprint/aidl/default/Session.cpp b/biometrics/fingerprint/aidl/default/Session.cpp index c928df4dd5..f6a03143a0 100644 --- a/biometrics/fingerprint/aidl/default/Session.cpp +++ b/biometrics/fingerprint/aidl/default/Session.cpp @@ -14,93 +14,216 @@ * limitations under the License. */ -#include +#include "Session.h" + #include -#include "Session.h" +#include "CancellationSignal.h" namespace aidl::android::hardware::biometrics::fingerprint { -class CancellationSignal : public common::BnCancellationSignal { - public: - ndk::ScopedAStatus cancel() override { return ndk::ScopedAStatus::ok(); } -}; +Session::Session(int sensorId, int userId, std::shared_ptr cb, + FakeFingerprintEngine* engine, WorkerThread* worker) + : mSensorId(sensorId), + mUserId(userId), + mCb(std::move(cb)), + mEngine(engine), + mWorker(worker), + mScheduledState(SessionState::IDLING), + mCurrentState(SessionState::IDLING) { + CHECK_GE(mSensorId, 0); + CHECK_GE(mUserId, 0); + CHECK(mEngine); + CHECK(mWorker); + CHECK(mCb); +} -Session::Session(std::shared_ptr cb) : mCb(std::move(cb)) {} +void Session::scheduleStateOrCrash(SessionState state) { + CHECK(mScheduledState == SessionState::IDLING); + CHECK(mCurrentState == SessionState::IDLING); + mScheduledState = state; +} -ndk::ScopedAStatus Session::generateChallenge(int32_t /*cookie*/, int32_t /*timeoutSec*/) { +void Session::enterStateOrCrash(int cookie, SessionState state) { + CHECK(mScheduledState == state); + mCurrentState = mScheduledState; + mScheduledState = SessionState::IDLING; + mCb->onStateChanged(cookie, mCurrentState); +} + +void Session::enterIdling(int cookie) { + mCurrentState = SessionState::IDLING; + mCb->onStateChanged(cookie, mCurrentState); +} + +bool Session::isClosed() { + return mCurrentState == SessionState::CLOSED; +} + +ndk::ScopedAStatus Session::generateChallenge(int32_t cookie, int32_t timeoutSec) { LOG(INFO) << "generateChallenge"; + scheduleStateOrCrash(SessionState::GENERATING_CHALLENGE); + + mWorker->schedule(Callable::from([this, cookie, timeoutSec] { + enterStateOrCrash(cookie, SessionState::GENERATING_CHALLENGE); + mEngine->generateChallengeImpl(mCb.get(), timeoutSec); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::revokeChallenge(int32_t /*cookie*/, int64_t /*challenge*/) { +ndk::ScopedAStatus Session::revokeChallenge(int32_t cookie, int64_t challenge) { LOG(INFO) << "revokeChallenge"; + scheduleStateOrCrash(SessionState::REVOKING_CHALLENGE); + + mWorker->schedule(Callable::from([this, cookie, challenge] { + enterStateOrCrash(cookie, SessionState::REVOKING_CHALLENGE); + mEngine->revokeChallengeImpl(mCb.get(), challenge); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::enroll(int32_t /*cookie*/, const keymaster::HardwareAuthToken& /*hat*/, - std::shared_ptr* /*out*/) { +ndk::ScopedAStatus Session::enroll(int32_t cookie, const keymaster::HardwareAuthToken& hat, + std::shared_ptr* out) { LOG(INFO) << "enroll"; + scheduleStateOrCrash(SessionState::ENROLLING); + + std::promise cancellationPromise; + auto cancFuture = cancellationPromise.get_future(); + + mWorker->schedule(Callable::from([this, cookie, hat, cancFuture = std::move(cancFuture)] { + enterStateOrCrash(cookie, SessionState::ENROLLING); + if (shouldCancel(cancFuture)) { + mCb->onError(Error::CANCELED, 0 /* vendorCode */); + } else { + mEngine->enrollImpl(mCb.get(), hat); + } + enterIdling(cookie); + })); + + *out = SharedRefBase::make(std::move(cancellationPromise)); return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::authenticate(int32_t /*cookie*/, int64_t /*keystoreOperationId*/, +ndk::ScopedAStatus Session::authenticate(int32_t cookie, int64_t operationId, std::shared_ptr* out) { LOG(INFO) << "authenticate"; - if (mCb) { - mCb->onStateChanged(0, SessionState::AUTHENTICATING); - } - *out = SharedRefBase::make(); + scheduleStateOrCrash(SessionState::AUTHENTICATING); + + std::promise cancPromise; + auto cancFuture = cancPromise.get_future(); + + mWorker->schedule( + Callable::from([this, cookie, operationId, cancFuture = std::move(cancFuture)] { + enterStateOrCrash(cookie, SessionState::AUTHENTICATING); + if (shouldCancel(cancFuture)) { + mCb->onError(Error::CANCELED, 0 /* vendorCode */); + } else { + mEngine->authenticateImpl(mCb.get(), operationId); + } + enterIdling(cookie); + })); + + *out = SharedRefBase::make(std::move(cancPromise)); return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::detectInteraction( - int32_t /*cookie*/, std::shared_ptr* /*out*/) { +ndk::ScopedAStatus Session::detectInteraction(int32_t cookie, + std::shared_ptr* out) { LOG(INFO) << "detectInteraction"; + scheduleStateOrCrash(SessionState::DETECTING_INTERACTION); + + std::promise cancellationPromise; + auto cancFuture = cancellationPromise.get_future(); + + mWorker->schedule(Callable::from([this, cookie, cancFuture = std::move(cancFuture)] { + enterStateOrCrash(cookie, SessionState::DETECTING_INTERACTION); + if (shouldCancel(cancFuture)) { + mCb->onError(Error::CANCELED, 0 /* vendorCode */); + } else { + mEngine->detectInteractionImpl(mCb.get()); + } + enterIdling(cookie); + })); + + *out = SharedRefBase::make(std::move(cancellationPromise)); return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::enumerateEnrollments(int32_t /*cookie*/) { +ndk::ScopedAStatus Session::enumerateEnrollments(int32_t cookie) { LOG(INFO) << "enumerateEnrollments"; - if (mCb) { - mCb->onStateChanged(0, SessionState::ENUMERATING_ENROLLMENTS); - mCb->onEnrollmentsEnumerated(std::vector()); - } + scheduleStateOrCrash(SessionState::ENUMERATING_ENROLLMENTS); + + mWorker->schedule(Callable::from([this, cookie] { + enterStateOrCrash(cookie, SessionState::ENUMERATING_ENROLLMENTS); + mEngine->enumerateEnrollmentsImpl(mCb.get()); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::removeEnrollments(int32_t /*cookie*/, - const std::vector& /*enrollmentIds*/) { +ndk::ScopedAStatus Session::removeEnrollments(int32_t cookie, + const std::vector& enrollmentIds) { LOG(INFO) << "removeEnrollments"; - if (mCb) { - mCb->onStateChanged(0, SessionState::REMOVING_ENROLLMENTS); - mCb->onEnrollmentsRemoved(std::vector()); - } + scheduleStateOrCrash(SessionState::REMOVING_ENROLLMENTS); + + mWorker->schedule(Callable::from([this, cookie, enrollmentIds] { + enterStateOrCrash(cookie, SessionState::REMOVING_ENROLLMENTS); + mEngine->removeEnrollmentsImpl(mCb.get(), enrollmentIds); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::getAuthenticatorId(int32_t /*cookie*/) { +ndk::ScopedAStatus Session::getAuthenticatorId(int32_t cookie) { LOG(INFO) << "getAuthenticatorId"; - if (mCb) { - mCb->onStateChanged(0, SessionState::GETTING_AUTHENTICATOR_ID); - mCb->onAuthenticatorIdRetrieved(0 /* authenticatorId */); - } + scheduleStateOrCrash(SessionState::GETTING_AUTHENTICATOR_ID); + + mWorker->schedule(Callable::from([this, cookie] { + enterStateOrCrash(cookie, SessionState::GETTING_AUTHENTICATOR_ID); + mEngine->getAuthenticatorIdImpl(mCb.get()); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::invalidateAuthenticatorId(int32_t /*cookie*/) { +ndk::ScopedAStatus Session::invalidateAuthenticatorId(int32_t cookie) { LOG(INFO) << "invalidateAuthenticatorId"; + scheduleStateOrCrash(SessionState::INVALIDATING_AUTHENTICATOR_ID); + + mWorker->schedule(Callable::from([this, cookie] { + enterStateOrCrash(cookie, SessionState::INVALIDATING_AUTHENTICATOR_ID); + mEngine->invalidateAuthenticatorIdImpl(mCb.get()); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::resetLockout(int32_t /*cookie*/, - const keymaster::HardwareAuthToken& /*hat*/) { +ndk::ScopedAStatus Session::resetLockout(int32_t cookie, const keymaster::HardwareAuthToken& hat) { LOG(INFO) << "resetLockout"; + scheduleStateOrCrash(SessionState::RESETTING_LOCKOUT); + + mWorker->schedule(Callable::from([this, cookie, hat] { + enterStateOrCrash(cookie, SessionState::RESETTING_LOCKOUT); + mEngine->resetLockoutImpl(mCb.get(), hat); + enterIdling(cookie); + })); + return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Session::close(int32_t /*cookie*/) { +ndk::ScopedAStatus Session::close(int32_t cookie) { LOG(INFO) << "close"; + CHECK(mCurrentState == SessionState::IDLING) << "Can't close a non-idling session. Crashing."; + mCurrentState = SessionState::CLOSED; + mCb->onStateChanged(cookie, mCurrentState); return ndk::ScopedAStatus::ok(); } @@ -119,4 +242,5 @@ ndk::ScopedAStatus Session::onUiReady() { LOG(INFO) << "onUiReady"; return ndk::ScopedAStatus::ok(); } + } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/include/CancellationSignal.h b/biometrics/fingerprint/aidl/default/include/CancellationSignal.h new file mode 100644 index 0000000000..99f2fbae97 --- /dev/null +++ b/biometrics/fingerprint/aidl/default/include/CancellationSignal.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 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 + +#include "WorkerThread.h" + +namespace aidl::android::hardware::biometrics::fingerprint { + +class CancellationSignal : public common::BnCancellationSignal { + public: + explicit CancellationSignal(std::promise&& cancellationPromise); + + ndk::ScopedAStatus cancel() override; + + private: + std::promise mCancellationPromise; +}; + +// Returns whether the given cancellation future is ready, i.e. whether the operation corresponding +// to this future should be cancelled. +bool shouldCancel(const std::future& cancellationFuture); + +} // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h new file mode 100644 index 0000000000..934331638e --- /dev/null +++ b/biometrics/fingerprint/aidl/default/include/FakeFingerprintEngine.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 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 + +namespace aidl::android::hardware::biometrics::fingerprint { + +class FakeFingerprintEngine { + public: + void generateChallengeImpl(ISessionCallback* cb, int32_t /*timeoutSec*/) { + LOG(INFO) << "generateChallengeImpl"; + cb->onChallengeGenerated(0 /* challenge */); + } + + void revokeChallengeImpl(ISessionCallback* cb, int64_t challenge) { + LOG(INFO) << "revokeChallengeImpl"; + cb->onChallengeRevoked(challenge); + } + + void enrollImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) { + LOG(INFO) << "enrollImpl"; + cb->onEnrollmentProgress(0 /* enrollmentId */, 0 /* remaining */); + } + + void authenticateImpl(ISessionCallback* cb, int64_t /*operationId*/) { + LOG(INFO) << "authenticateImpl"; + cb->onAuthenticationSucceeded(0 /* enrollmentId */, {} /* hat */); + } + + void detectInteractionImpl(ISessionCallback* cb) { + LOG(INFO) << "detectInteractionImpl"; + cb->onInteractionDetected(); + } + + void enumerateEnrollmentsImpl(ISessionCallback* cb) { + LOG(INFO) << "enumerateEnrollmentsImpl"; + cb->onEnrollmentsEnumerated({} /* enrollmentIds */); + } + + void removeEnrollmentsImpl(ISessionCallback* cb, const std::vector& enrollmentIds) { + LOG(INFO) << "removeEnrollmentsImpl"; + cb->onEnrollmentsRemoved(enrollmentIds); + } + + void getAuthenticatorIdImpl(ISessionCallback* cb) { + LOG(INFO) << "getAuthenticatorIdImpl"; + cb->onAuthenticatorIdRetrieved(0 /* authenticatorId */); + } + + void invalidateAuthenticatorIdImpl(ISessionCallback* cb) { + LOG(INFO) << "invalidateAuthenticatorIdImpl"; + cb->onAuthenticatorIdInvalidated(0 /* newAuthenticatorId */); + } + + void resetLockoutImpl(ISessionCallback* cb, const keymaster::HardwareAuthToken& /*hat*/) { + LOG(INFO) << "resetLockoutImpl"; + cb->onLockoutCleared(); + } +}; + +} // namespace aidl::android::hardware::biometrics::fingerprint \ No newline at end of file diff --git a/biometrics/fingerprint/aidl/default/include/Fingerprint.h b/biometrics/fingerprint/aidl/default/include/Fingerprint.h index ce1366cf4c..9b8eef85ea 100644 --- a/biometrics/fingerprint/aidl/default/include/Fingerprint.h +++ b/biometrics/fingerprint/aidl/default/include/Fingerprint.h @@ -18,9 +18,13 @@ #include +#include "FakeFingerprintEngine.h" +#include "Session.h" +#include "WorkerThread.h" + namespace aidl::android::hardware::biometrics::fingerprint { -class Fingerprint final : public BnFingerprint { +class Fingerprint : public BnFingerprint { public: Fingerprint(); @@ -31,6 +35,11 @@ class Fingerprint final : public BnFingerprint { std::shared_ptr* out) override; ndk::ScopedAStatus reset() override; + + private: + std::unique_ptr mEngine; + WorkerThread mWorker; + std::weak_ptr mSession; }; } // namespace aidl::android::hardware::biometrics::fingerprint diff --git a/biometrics/fingerprint/aidl/default/include/Session.h b/biometrics/fingerprint/aidl/default/include/Session.h index 99d806b5e5..adda8310f4 100644 --- a/biometrics/fingerprint/aidl/default/include/Session.h +++ b/biometrics/fingerprint/aidl/default/include/Session.h @@ -19,6 +19,9 @@ #include #include +#include "FakeFingerprintEngine.h" +#include "WorkerThread.h" + namespace aidl::android::hardware::biometrics::fingerprint { namespace common = aidl::android::hardware::biometrics::common; @@ -26,7 +29,8 @@ namespace keymaster = aidl::android::hardware::keymaster; class Session : public BnSession { public: - explicit Session(std::shared_ptr cb); + Session(int sensorId, int userId, std::shared_ptr cb, + FakeFingerprintEngine* engine, WorkerThread* worker); ndk::ScopedAStatus generateChallenge(int32_t cookie, int32_t timeoutSec) override; @@ -35,7 +39,7 @@ class Session : public BnSession { ndk::ScopedAStatus enroll(int32_t cookie, const keymaster::HardwareAuthToken& hat, std::shared_ptr* out) override; - ndk::ScopedAStatus authenticate(int32_t cookie, int64_t keystoreOperationId, + ndk::ScopedAStatus authenticate(int32_t cookie, int64_t operationId, std::shared_ptr* out) override; ndk::ScopedAStatus detectInteraction( @@ -62,8 +66,29 @@ class Session : public BnSession { ndk::ScopedAStatus onUiReady() override; + bool isClosed(); + private: + // Crashes the HAL if it's not currently idling because that would be an invalid state machine + // transition. Otherwise, sets the scheduled state to the given state. + void scheduleStateOrCrash(SessionState state); + + // Crashes the HAL if the provided state doesn't match the previously scheduled state. + // Otherwise, transitions into the provided state, clears the scheduled state, and notifies + // the client about the transition by calling ISessionCallback#onStateChanged. + void enterStateOrCrash(int cookie, SessionState state); + + // Sets the current state to SessionState::IDLING and notifies the client about the transition + // by calling ISessionCallback#onStateChanged. + void enterIdling(int cookie); + + int32_t mSensorId; + int32_t mUserId; std::shared_ptr mCb; + FakeFingerprintEngine* mEngine; + WorkerThread* mWorker; + SessionState mScheduledState; + SessionState mCurrentState; }; } // namespace aidl::android::hardware::biometrics::fingerprint