Add the authentication loop
Arms a scan session and drives the frame loop: capture, decide finger from the calibrated floor, report the touch edges, classify the verdict. It needs no writes of any kind -- no SAVE_DATA, no RPMB write, no SFS write -- so it runs safely against an existing template with the store read-only. That is what makes it the right thing to try before enrolment rather than after. Verified armed on the phone: the template loads, the floor calibrates, and AUTHENTICATE returns rc=0, which also proves the gid agrees with the one SET_ACTIVE_GROUP used (a mismatch answers -200). With no finger present the loop correctly reports nothing: no touch edge, no event, no terminal frame. The fid field is poisoned before every REPORT_EVENT, because a zero-initialised buffer cannot distinguish "the matcher never ran" from "the matcher ran and rejected" -- the failure path writes zero there too. The tally reports terminal frames as the denominator and presses separately, so a run cannot be read as having rejections it did not have.
This commit is contained in:
parent
03023284ba
commit
c33076ca9b
2 changed files with 105 additions and 1 deletions
|
|
@ -53,6 +53,9 @@ constexpr const char* Version = "0.0.3";
|
|||
|
||||
bool g_verbose = false;
|
||||
bool g_listeners = false;
|
||||
bool g_auth = false;
|
||||
int g_frames = 40;
|
||||
int g_frameGapMs = 500;
|
||||
std::uint32_t g_gid = 0;
|
||||
std::string g_taPath = "/lib/firmware/focal64.mbn";
|
||||
std::string g_cfgPath = "/lib/firmware/fingerprintd.json";
|
||||
|
|
@ -741,7 +744,17 @@ qcomtee_object* LoadTrustlet(qcomtee_object* loader, const std::string& path) {
|
|||
// sendRequest is op 0 with arity 0x0424: four input buffers, two output, four
|
||||
// object slots. The request and response buffers go in and come back out; the
|
||||
// trustlet's own return code rides in the returned request's header.
|
||||
struct CommandResult { bool invoked = false; qcomtee_result_t result = 0; std::int32_t rc = 0; std::int32_t metric = 0; };
|
||||
struct CommandResult {
|
||||
bool invoked = false;
|
||||
qcomtee_result_t result = 0;
|
||||
std::int32_t rc = 0;
|
||||
std::int32_t metric = 0;
|
||||
// Only meaningful for REPORT_EVENT: the matcher's verdict rides in the
|
||||
// returned request's payload.
|
||||
std::uint32_t gid = 0;
|
||||
std::uint32_t fid = 0;
|
||||
std::int32_t samplesRemaining = -1;
|
||||
};
|
||||
|
||||
CommandResult SendCommand(qcomtee_object* app, fingerprintd::ta::Cmd cmd,
|
||||
std::span<const std::byte> payload) {
|
||||
|
|
@ -816,6 +829,11 @@ CommandResult SendCommand(qcomtee_object* app, fingerprintd::ta::Cmd cmd,
|
|||
out.invoked = true;
|
||||
out.rc = ta::ResultCode(reqOut);
|
||||
out.metric = ta::CaptureMetric(reqOut);
|
||||
if (cmd == ta::Cmd::ReportEvent) {
|
||||
out.gid = ta::MatchedGid(reqOut);
|
||||
out.fid = ta::MatchedFid(reqOut);
|
||||
out.samplesRemaining = ta::SamplesRemaining(reqOut);
|
||||
}
|
||||
if (g_verbose && cmd == ta::Cmd::CaptureImage) {
|
||||
std::string hex;
|
||||
for (std::size_t i = 0; i < 0x30; i++)
|
||||
|
|
@ -1008,6 +1026,89 @@ int Probe() {
|
|||
std::println("idle floor = {}, finger threshold = {}", baseline.Floor(),
|
||||
baseline.Threshold());
|
||||
|
||||
// ---- Authentication
|
||||
//
|
||||
// Needs no writes of any kind: no SAVE_DATA, no RPMB write, no SFS write.
|
||||
// So it runs safely against an existing template with the store read-only,
|
||||
// which is what makes it the right thing to try before enrolment.
|
||||
if (g_auth) {
|
||||
namespace ta = fingerprintd::ta;
|
||||
namespace en = fingerprintd::engine;
|
||||
|
||||
// AUTHENTICATE arms the scan session. Its gid must match the one
|
||||
// SET_ACTIVE_GROUP used or the trustlet answers -200.
|
||||
std::vector<std::byte> au(ta::AuthPayloadSize);
|
||||
ta::BuildAuthPayload(au, 1, g_gid);
|
||||
std::println("\nAUTHENTICATE gid={}", g_gid);
|
||||
auto a = SendCommand(app, ta::Cmd::Authenticate, au);
|
||||
Report(ta::Cmd::Authenticate, a);
|
||||
if (!a.invoked || a.result != 0 || a.rc != 0) {
|
||||
std::println(std::cerr, "could not arm authentication");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int c = 3; c > 0; c--) {
|
||||
std::println("*** press and lift your finger in {}... ***", c);
|
||||
std::fflush(stdout);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
std::println("\n*** GO -- {} frames, about {} seconds ***\n", g_frames,
|
||||
(g_frames * g_frameGapMs) / 1000);
|
||||
en::TouchTracker tracker;
|
||||
en::AuthTally tally;
|
||||
|
||||
for (int i = 0; i < g_frames; i++) {
|
||||
std::vector<std::byte> q(0x10, std::byte{0});
|
||||
SendCommand(app, ta::Cmd::QueryEventStatus, q);
|
||||
|
||||
std::vector<std::byte> cap(ta::CaptureDeclaredLen);
|
||||
ta::BuildCapturePayload(cap);
|
||||
auto c = SendCommand(app, ta::Cmd::CaptureImage, cap);
|
||||
bool finger = baseline.IsFinger(c.metric);
|
||||
|
||||
auto events = tracker.Observe(finger, en::Mode::Authenticate);
|
||||
std::string verdicts;
|
||||
for (ta::Event ev : events) {
|
||||
std::vector<std::byte> evbuf(ta::EventContextSize);
|
||||
ta::BuildEventContext(evbuf, { .event = ev });
|
||||
// Poison the fid field before the call. A zero-initialised
|
||||
// buffer cannot tell "the matcher never ran" from "the matcher
|
||||
// ran and rejected the finger" -- the failure path writes zero
|
||||
// there too, so zero is ambiguous and 0xAAAAAAAA is not.
|
||||
ta::PoisonFid(std::span(evbuf).subspan(ta::ResponsePayloadOff));
|
||||
|
||||
auto r = SendCommand(app, ta::Cmd::ReportEvent, evbuf);
|
||||
if (!r.invoked) continue;
|
||||
|
||||
ta::Verdict v = ta::Classify(r.rc, r.fid);
|
||||
tally.Observe(v, finger);
|
||||
verdicts += std::format(" {}", [&] {
|
||||
switch (v) {
|
||||
case ta::Verdict::Match:
|
||||
return std::format("*** MATCH *** gid={} fid={}", r.gid, r.fid);
|
||||
case ta::Verdict::Rejected: return std::string("REJECTED");
|
||||
case ta::Verdict::NotIdentifiedYet: return std::string("not identified yet");
|
||||
case ta::Verdict::MatcherNeverRan: return std::string("released");
|
||||
}
|
||||
return std::string("?");
|
||||
}());
|
||||
}
|
||||
std::println(" frame {:2}/{}: metric={:<4}{}{}", i + 1, g_frames, c.metric,
|
||||
finger ? " FINGER" : " ", verdicts);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
||||
}
|
||||
// Only a terminal verdict is an attempt. Counting rescan frames as
|
||||
// rejections invents failures that never happened.
|
||||
std::println("\n=== {} MATCH / {} REJECTED over {} terminal frames ===",
|
||||
tally.Matches(), tally.Rejections(), tally.TerminalFrames());
|
||||
std::println(" ({} answered 'not identified yet', {} never reached the matcher)",
|
||||
tally.NotIdentifiedYet(), tally.NeverRan());
|
||||
if (tally.Presses() > 0)
|
||||
std::println(" presses: {} total, {} reached a verdict, {} matched",
|
||||
tally.Presses(), tally.PressesDecided(), tally.PressesMatched());
|
||||
std::println(" {}", tally.Identified() ? "FINGER IDENTIFIED" : "no match");
|
||||
}
|
||||
|
||||
std::println("\ntrustlet initialised against a powered sensor.");
|
||||
pthread_cancel(th);
|
||||
pthread_join(th, nullptr);
|
||||
|
|
@ -1033,6 +1134,8 @@ int main(int argc, char** argv) {
|
|||
// which destroys an enrolled template. Opt in explicitly.
|
||||
if (a == "--sfs-writable") g_sfsReadOnly = false;
|
||||
if (a == "--rpmb-write") g_rpmbWrite = true;
|
||||
if (a == "--auth") { g_auth = true; g_listeners = true; }
|
||||
if (a.starts_with("--frames=")) g_frames = std::stoi(std::string(a.substr(9)));
|
||||
if (a.starts_with("--sfs-root=")) g_sfsRoot = a.substr(11);
|
||||
if (a.starts_with("--gid=")) g_gid = static_cast<std::uint32_t>(
|
||||
std::stoul(std::string(a.substr(6))));
|
||||
|
|
|
|||
Loading…
Reference in a new issue