Thirteen real fprintd-verify runs produced four matches and nine misses and no
rate, because nothing recorded which finger each run used. This script runs the
protocol -- N quick taps with the enrolled finger, then N with a different one
-- tells the user which finger and which tap they are on, and records the label
and the wall-clock latency from prompt to client result per tap. The daemon's
transcript has the frames; this has the labels. A loop change is measured
against this, one variable at a time.
The probe becomes the thing the plan was for. Three threads: the supplicant
services QTEE's callbacks; the worker owns the sensor rail, the QTEE session
and the trustlet and is the only thread that ever invokes it, so every
enrolment and authentication is serialised by construction; the main thread
runs the GLib loop and speaks fprintd's own D-Bus interface, never touching
the trustlet directly.
Session is the bring-up from a cold /dev/tee0 to a calibrated sensor, plus the
enrol and verify loops as methods that take a cancel flag and progress
callbacks. Worker is a job queue on a pthread with an 8 MiB stack -- musl's
default is 128 KiB and the session keeps request buffers on the stack. Results
come back through g_idle_add so signals are emitted on the thread that owns
the connection.
net.reactivated.Fprint is implemented rather than wrapped: Manager with
GetDevices/GetDefaultDevice, Device with Claim/Release, EnrollStart/Stop,
VerifyStart/Stop, ListEnrolledFingers and the three Delete variants, the three
signals, and the five properties. Owning fprintd's name is what lets
pam_fprintd, the Plasma KCM and fprintd-enroll work unmodified.
Two honest limits. Authorisation is the conservative rule -- you may act on
your own prints, root on anyone's -- because polkit is not in this milestone.
And DeleteEnrolledFingers removes the finger's NAME only: FF_CMD_TA_REMOVE
exists but its payload is not reverse-engineered, and guessing at a command
that writes to the store is exactly how an index got invalidated earlier
today. A deleted finger loses its name and stops being offered; its template
still occupies a slot in the group. Logged as such.
The finger-name map is written per user under the state directory, tmp-file
and rename. An enrolment records the fid the trustlet reported in the touch
event's response; if none was reported the finger cannot be named yet, and
the daemon says so rather than inventing one.
Verified on the phone as a systemd unit: owns the bus name, init chain
complete, floor calibrated, ready.
fingerprintd's own code now talks to QTEE. On the phone:
root object on /dev/tee0
client env obtained (uid 0, 13-byte credentials)
QSEECOM-compat app loader (UID 122) opened
The credentials object is ours rather than libqcomtee's. Upstream's exists only
to build a thirteen-byte CBOR map and drags in QCBOR to do it, so
packaging/make-libqcomtee.sh compiles the two sources that matter and drops
credentials_obj.c entirely -- nothing else references it, and the library then
has no dependency beyond libc. The map is built in Fingerprintd:Tee where it is
pinned byte-for-byte against the string verified on-device, and the object's
two-op read protocol is served here.
Three interop details, all of which cost a build cycle:
* libqcomtee's headers carry no extern "C" guard, having only ever been
consumed from C, so everything came out C++-mangled. They also pull in
<stdatomic.h> and <stdio.h>, which under libc++ drag in templates that may
not appear inside extern "C" -- so those are included first.
* tee_call_t's second parameter is unsigned long on glibc and int on musl.
The native build is glibc and the phone is musl; both forms are compiled.
* On the callback path a UBUF_OUTPUT param arrives with addr = NULL. The
dispatcher supplies the buffer, so a handler POINTS the param at its own
storage rather than writing through the incoming address. Doing the latter
is a null dereference that takes the supplicant thread with it, which is
how the first run against real QTEE ended -- with the correct behaviour
already spelled out in the module comment above the code that ignored it.
That comment now says so in as many words.
packaging/make-sysroot.sh populates a minimal Alpine aarch64 rootfs with
apk-tools-static, so a cross build needs no root, no qemu and no phone. Adapted
from imsd's, plus glib-dev for the bus layer that is coming.
Building against a real Alpine sysroot means the binary links dynamically
against the phone's own musl and libc++ -- libc++, libc++abi, libunwind and
libgcc_s are all already on pmOS. The harness this daemon replaces had to be
built -static, but only because it was built with the host's glibc toolchain;
that constraint was never about the target.
The pipeline is proven end to end rather than assumed: the cross-built binary
runs on the phone, and all five core suites pass there too -- on the real
hardware, not just under qemu-aarch64. That matters for modules that are almost
entirely little-endian field packing and offset arithmetic.