Judge verification per press, and log every frame of it
A wrong finger against the daemon was a silent wait: the loop exited only on a terminal verdict, and at the stock rescan budget a wrong finger never produces one -- it answers "not identified yet" on every frame until the right finger shows up. fprintd's PAM module needs a verify-no-match to deny or retry, so that is a client that hangs. The unit of decision is now a press. Within one press the matcher may reject early frames and match a later one, so a press is judged when the finger lifts: any match wins, only rejections is no-match, no terminal frame at all is undecided and scanning continues. A press that matches is reported the moment it does rather than making the user hold for a release. Undecided presses are exactly why the rescan budget matters here. At 0 every frame is terminal and every press decides; at the stock budget seven of nineteen correct-finger presses ended undecided in the last measurement, which under a press rule would read as rejections. So the daemon is being run with rescan forced to 0 while that trade-off is measured on the new template rather than assumed. Every frame is logged under --verbose and every press always, so the next wrong-finger control is visible in the transcript instead of absent from it.
This commit is contained in:
parent
0809b95a48
commit
50617f994f
1 changed files with 57 additions and 9 deletions
|
|
@ -1200,14 +1200,27 @@ public:
|
|||
|
||||
// ---- Verification
|
||||
//
|
||||
// Runs until the first terminal verdict or a cancel. A frame is one of
|
||||
// three things and only the third is a verdict: release (poison intact),
|
||||
// rescan (rc=-11), or match/reject.
|
||||
// The unit of decision is a PRESS, not a frame. A frame is one of three
|
||||
// things -- release (poison intact), rescan (rc=-11), match/reject -- and
|
||||
// within one press the matcher may reject early frames and match a later
|
||||
// one, so a press is judged when the finger LIFTS: any match wins; only
|
||||
// rejections means no-match; no terminal frame at all means undecided,
|
||||
// and scanning continues into the next press.
|
||||
//
|
||||
// That last case is why the rescan budget matters here. At the stock
|
||||
// budget a wrong finger answers "not identified yet" on every frame and
|
||||
// never yields a terminal rejection, so its presses are all undecided and
|
||||
// a client waits forever -- fprintd's PAM module needs a verify-no-match
|
||||
// to deny or retry. With max_authentication_rescan_times at 0 every frame
|
||||
// is terminal and every press decides. A wrong finger stops being a
|
||||
// silent wait.
|
||||
struct VerifyOutcome {
|
||||
bool decided = false;
|
||||
bool matched = false;
|
||||
bool cancelled = false;
|
||||
std::uint32_t fid = 0;
|
||||
int presses = 0;
|
||||
int frames = 0;
|
||||
};
|
||||
|
||||
VerifyOutcome Verify(std::uint32_t gid, std::atomic<bool>& cancel, int maxFrames) {
|
||||
|
|
@ -1226,6 +1239,10 @@ public:
|
|||
if (!a.Ok()) return out;
|
||||
|
||||
en::TouchTracker tracker;
|
||||
bool inPress = false, pressMatched = false, pressRejected = false;
|
||||
int pressFrames = 0, rescans = 0;
|
||||
std::uint32_t pressFid = 0;
|
||||
|
||||
// The reference frame loop is {QUERY, CAPTURE, REPORT, QUERY, REPORT}.
|
||||
// The trailing query acknowledges the trustlet's event state; without
|
||||
// it, after the first verdict every later frame answers "not
|
||||
|
|
@ -1239,8 +1256,15 @@ public:
|
|||
auto c = SendCommand(app_, ta::Cmd::CaptureImage, cap);
|
||||
bool finger = baseline_.IsFinger(c.metric);
|
||||
fingerPresent_.store(finger);
|
||||
out.frames++;
|
||||
|
||||
std::string note;
|
||||
for (ta::Event ev : tracker.Observe(finger, en::Mode::Authenticate)) {
|
||||
if (ev == ta::Event::FingerTouched) {
|
||||
inPress = true; pressMatched = false; pressRejected = false;
|
||||
pressFrames = 0; rescans = 0; pressFid = 0;
|
||||
out.presses++;
|
||||
}
|
||||
std::vector<std::byte> evbuf(ta::EventContextSize);
|
||||
ta::BuildEventContext(evbuf, { .event = ev });
|
||||
// A zero-initialised buffer cannot tell "the matcher never
|
||||
|
|
@ -1250,14 +1274,35 @@ public:
|
|||
auto r = SendCommand(app_, ta::Cmd::ReportEvent, evbuf);
|
||||
if (!r.invoked) continue;
|
||||
ta::Verdict v = ta::Classify(r.rc, r.fid);
|
||||
if (ta::IsTerminal(v)) {
|
||||
out.decided = true;
|
||||
out.matched = (v == ta::Verdict::Match);
|
||||
out.fid = out.matched ? r.fid : 0;
|
||||
std::println(" verdict: {} fid={}", out.matched ? "MATCH" : "REJECTED", out.fid);
|
||||
break;
|
||||
switch (v) {
|
||||
case ta::Verdict::Match: pressMatched = true; pressFid = r.fid; note += " MATCH"; break;
|
||||
case ta::Verdict::Rejected: pressRejected = true; note += " rej"; break;
|
||||
case ta::Verdict::NotIdentifiedYet: rescans++; note += " -11"; break;
|
||||
case ta::Verdict::MatcherNeverRan: break;
|
||||
}
|
||||
if (ev == ta::Event::FingerReleased && inPress) {
|
||||
// The press is over: judge it.
|
||||
inPress = false;
|
||||
if (pressMatched) {
|
||||
out.decided = true; out.matched = true; out.fid = pressFid;
|
||||
} else if (pressRejected) {
|
||||
out.decided = true; out.matched = false;
|
||||
}
|
||||
std::println(" press {}: {} frames, {} rescans -> {}", out.presses, pressFrames,
|
||||
rescans, pressMatched ? std::format("MATCH fid={}", pressFid)
|
||||
: pressRejected ? "NO MATCH" : "undecided");
|
||||
}
|
||||
}
|
||||
if (finger) pressFrames++;
|
||||
// A press that matched is decided the moment it does; do not make
|
||||
// the user keep holding for a release.
|
||||
if (pressMatched && !out.decided) {
|
||||
out.decided = true; out.matched = true; out.fid = pressFid;
|
||||
std::println(" press {}: {} frames -> MATCH fid={}", out.presses, pressFrames, pressFid);
|
||||
}
|
||||
if (g_verbose)
|
||||
std::println(" frame {:3}: metric={:<4}{}{}", i + 1, c.metric,
|
||||
finger ? " FINGER" : " ", note);
|
||||
SendCommand(app_, ta::Cmd::QueryEventStatus, q);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(g_frameGapMs));
|
||||
}
|
||||
|
|
@ -1434,6 +1479,9 @@ private:
|
|||
}
|
||||
case Job::Kind::Verify: {
|
||||
auto o = session_.Verify(j.uid, cancel_, /*maxFrames*/ 600);
|
||||
std::println("verify: {} over {} press(es), {} frame(s)",
|
||||
o.cancelled ? "cancelled" : !o.decided ? "undecided"
|
||||
: o.matched ? "MATCH" : "NO MATCH", o.presses, o.frames);
|
||||
auto ev = std::make_unique<Event>(Event{ .kind = Event::Kind::VerifyStatus });
|
||||
ev->done = true;
|
||||
ev->fid = o.fid;
|
||||
|
|
|
|||
Loading…
Reference in a new issue