From 231a1860ac0ad5e8535192d9c45de64fe4207b1f Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 5 Sep 2026 01:25:03 +0200 Subject: [PATCH] Do not make the trustlet re-read the whole template on every claim Selecting a group makes the trustlet reload every template in it from storage, and on this device that is 651276 bytes crossing the listener in chunks. Measured over ten verifications: eleven claims, eleven reloads, fifty-four chunk reads, twenty-six megabytes of round trips. All of it happens before the verify loop starts, so all of it lands in the latency a user feels while being invisible to the daemon's own timing -- which is how it survived this long. It is also unnecessary. The trustlet holds templates in memory once loaded; a successful authentication on stock produces no storage callbacks at all. A group that is already active does not need selecting again, so a repeat claim now answers from what the last real selection reported. Enrolment forces a real selection, because it adds a template and the reload is the point. Measured after: two claims, one real selection, one skipped, and the chunk reads drop from five per claim to two for the pair. --- implementations/main.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/implementations/main.cpp b/implementations/main.cpp index 36285c1..46a4716 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -1206,8 +1206,25 @@ public: // Select a group and count what loads. A template reload needs the init // chain to have run first -- Start() guarantees that. - int SetActiveGroup(std::uint32_t gid) { + // Selecting a group makes the trustlet RELOAD every template in it from + // storage, and on this device that is 651276 bytes crossing the listener in + // chunks -- about a megabyte of round trips per call. Measured 2026-09-05: + // ten verifications cost eleven reloads and 26 MB, all of it before the + // verify loop even starts, so all of it lands in the latency the user sees + // while being invisible to the daemon's own timing. + // + // It is also unnecessary. The trustlet holds templates in memory once + // loaded -- a successful authentication on stock produces no storage + // callbacks at all -- so a group that is already active does not need + // selecting again. `force` is for the cases that genuinely change the + // store: an enrolment, or a template removed underneath us. + int SetActiveGroup(std::uint32_t gid, bool force = false) { namespace ta = fingerprintd::ta; + if (!force && gid == gid_ && templatesLoaded_ >= 0) { + std::println("SET_ACTIVE_GROUP gid={} skipped (already active, {} template(s))", + gid, templatesLoaded_); + return templatesLoaded_; + } auto sag = ta::BuildSetActiveGroup(gid, g_groupPath); std::println("SET_ACTIVE_GROUP gid={}", gid); auto g = SendCommand(app_, ta::Cmd::SetActiveGroup, sag); @@ -1216,7 +1233,8 @@ public: Report(ta::Cmd::Enumerate, e); std::println(" templates loaded: {}", e.rc); gid_ = gid; - return e.invoked ? e.rc : -1; + templatesLoaded_ = e.invoked ? e.rc : -1; + return templatesLoaded_; } // ---- Enrolment @@ -1244,7 +1262,8 @@ public: out.why = "store is read-only or RPMB writes disabled"; return out; } - if (gid != gid_) SetActiveGroup(gid); + // An enrolment adds a template, so this one reloads for real. + SetActiveGroup(gid, /*force*/ true); // Stock's opening sequence. AUTHENTICATE is what arms the capture // session; CANCEL and RESET_LOCKOUT bracket it. @@ -1755,6 +1774,9 @@ private: Sensor sensor_; fingerprintd::engine::Baseline baseline_; std::uint32_t gid_ = 0xFFFFFFFF; + // What the last real SET_ACTIVE_GROUP reported, so a repeat claim can be + // answered without making the trustlet re-read the store. -1 = unknown. + int templatesLoaded_ = -1; // Set when a fold succeeded; cleared by the save. bool templateDirty_ = false; // Frames folded during the verify loop itself, carried into the harvest so