fingerprintd/packaging/fplearn.sh

213 lines
9.3 KiB
Shell
Raw Normal View History

#!/bin/sh
# fplearn.sh -- the template-learning measurement, as a protocol rather than a
# pile of remembered commands.
#
# fplearn.sh snapshot [name] save the current template aside
# fplearn.sh restore <name> put a saved template back and reload it
# fplearn.sh wipe remove every stored template (backup first)
# fplearn.sh enrol [finger] enrol at the config's sample count
# fplearn.sh base [n] [w] trial with learning OFF (the baseline)
# fplearn.sh trend [n] [w] three trials with learning ON
# fplearn.sh sizes just print the template container sizes
#
# SNAPSHOT BEFORE EVERY MEASUREMENT BLOCK. Learning rewrites the template in
# place, so without a snapshot an improvement cannot be attributed: a rate that
# climbs over an evening is equally explained by the template getting better, by
# the SENSOR being wiped clean, or by the USER learning where the sensor likes
# to be pressed (Jorijn, 2026-09-04, having spotted both while the numbers were
# still going up). All three move the same way on the same time axis.
#
# A snapshot makes the paired test possible: measure the learned template, then
# restore the older one and measure again WITHIN THE SAME SESSION, so the sensor
# is equally clean and the user equally practised for both halves. A rate that
# falls back on restore is learning. A rate that stays up is not.
#
# WIPE FIRST when re-enrolling a finger that is already in the group. Measured
# 2026-09-04: the duplicated-finger check refuses a finger the group already
# holds -- 0 accepted of 7 presses, rc=0 on every one, while a never-enrolled
# finger progressed normally. Re-enrolment ADDS a template and there is no
# trustlet-side remove, so the old one has to go first. `enrol` refuses a
# finger name that is already in the map and points here.
#
# WHY IT IS SHAPED LIKE THIS. Learning is cumulative: every matched press folds
# frames into the stored template, so a second run is not a repeat of the first
# and an A/B against a moving template is not an A/B at all. The only honest
# comparison is on ONE template lineage, in order:
#
# 1. enrol a fresh template, 20 samples, no position prompts --
# which is also the outstanding replication of the 7/10
# result, the one measurement this lane was parked on
# 2. base learning off: the clean number for THIS template, and
# the only figure comparable to every rate in the journal
# 3. trend learning on, three times: the rate should climb, and
# the container size is an independent witness that it
# is the template moving and not the weather
#
# Do NOT read run 1 of the trend as "learning made it better". Run 1 starts on
# the same template the baseline ended on; it is the first run that can improve
# it, not one that has already been improved.
set -u
GROUP=/mnt/persist/data/RIY7A+mQm3EA4FsCUmkJo0b9dFUYP2YZ4P5hmMiZgeA_Alt
MAP=/var/lib/fingerprintd/fingers-10000.map
UNIT=fingerprintd-test
BIN=/tmp/fingerprintd
COMMON="--daemon --verbose --edge-wake --sfs-root=/var/lib/fingerprintd/sfs --sfs-writable --rpmb-write"
# How many frames learning actually folded, out of the daemon's own transcript.
# Without this a trend run cannot tell "learning fired and did not help" from
# "learning never fired" -- which is exactly the confusion that made the first
# trend run measure a static template three times.
folds() {
L=$(ls -t /var/log/fingerprintd/*.log 2>/dev/null | head -1)
[ -n "$L" ] || { echo " (no transcript)"; return; }
m=$(grep -c -- "-> MATCH fid=" "$L" 2>/dev/null || echo 0)
f=$(grep -c "folded in (metric" "$L" 2>/dev/null || echo 0)
s=$(grep -c "learn: template saved" "$L" 2>/dev/null || echo 0)
echo " folds: $f frame(s) over $m matched press(es), $s save(s)"
}
sizes() {
# A template is stored twice, the container and its backup, so the sizes
# come in pairs. The BODY is the container minus its 4096-byte header.
sudo ls -la "$GROUP" 2>/dev/null | awk '$5 > 200000 { printf " %9d body %9d %s\n", $5, $5-4096, $9 }' | sort -u
}
restart() { # $1 = extra args
sudo systemctl stop "$UNIT" 2>/dev/null
sleep 2
sudo systemd-run --unit="$UNIT" --collect $BIN $COMMON $1 >/dev/null 2>&1
printf 'daemon starting'
i=0
while [ $i -lt 40 ]; do
if busctl --system list 2>/dev/null | grep -q net.reactivated.Fprint; then
# Owning the name is not the same as being ready: the session comes
# up on the worker thread afterwards.
sleep 6; printf ' ready\n'; return 0
fi
printf '.'; sleep 1; i=$((i+1))
done
printf ' TIMED OUT\n'; return 1
}
SNAPDIR=$HOME/fp6-backups/snapshots
case "${1:-}" in
snapshot)
NAME=${2:-$(date +%Y%m%d-%H%M%S)}
D=$SNAPDIR/$NAME
mkdir -p "$D"
n=0
for f in $(sudo ls "$GROUP"); do
sz=$(sudo stat -c %s "$GROUP/$f")
if [ "$sz" -gt 200000 ]; then sudo cp -p "$GROUP/$f" "$D/$f"; n=$((n+1)); fi
done
sudo cp -p "$MAP" "$D/" 2>/dev/null || true
sudo chown -R "$(id -u):$(id -g)" "$D"
echo "snapshot '$NAME': $n container(s)"
ls -la "$D" | awk 'NR>3{printf " %9d %s\n", $5, $9}'
echo "restore with: fplearn.sh restore $NAME" ;;
restore)
NAME=${2:-}
D=$SNAPDIR/$NAME
[ -n "$NAME" ] && [ -d "$D" ] || { echo "usage: fplearn.sh restore <name>"; echo "available:"; ls "$SNAPDIR" 2>/dev/null | sed 's/^/ /'; exit 1; }
echo "=== restore snapshot '$NAME' ==="
echo "before:"; sizes
# The trustlet holds the template in memory once loaded, so a restore that
# does not restart the daemon changes the file and nothing else.
sudo systemctl stop "$UNIT" 2>/dev/null; sleep 2
for f in $(ls "$D"); do
case "$f" in fingers-*.map) sudo cp -p "$D/$f" "$MAP" ;;
*) sudo cp -p "$D/$f" "$GROUP/$f" ;; esac
done
sudo sync
restart "--learn=0" || exit 1
echo "after:"; sizes
fprintd-list user 2>&1 | tail -1
echo
echo "Daemon is up with learning OFF, so the restored template stays put."
echo "Measure it now, in this session, against the block you just ran:"
echo " fplearn.sh base" ;;
sizes)
echo "template containers now:"; sizes ;;
wipe)
# The procedure of 2026-09-03 and 2026-09-04, and its reasoning: ONLY the
# template containers go (>200000 bytes; each template is stored twice).
# The 1588-byte group index is KEPT -- deleting it risks the RPMB
# anti-rollback counters going stale against object ids QTEE would then
# recreate, which already cost an index restore once -- and a missing
# template container is the known-good "not exist, skip" case. The daemon
# is stopped first so the trustlet reloads from the store, and a backup is
# taken first because a template is not reproducible without a finger.
echo "=== wipe: every stored template ==="
echo "before:"; sizes
D=$HOME/fp6-backups/$(date +%Y-%m-%d-%H%M)-pre-wipe
mkdir -p "$D"
sudo tar -cf "$D/persist-data.tar" -C /mnt/persist data
sudo cp "$MAP" "$D/" 2>/dev/null || true
sudo chown -R "$(id -u):$(id -g)" "$D"
echo "backup: $D ($(tar -tf "$D/persist-data.tar" | wc -l) entries, $(sha256sum "$D/persist-data.tar" | cut -c1-16))"
sudo systemctl stop "$UNIT" 2>/dev/null; sleep 2
n=0
for f in $(sudo ls "$GROUP"); do
sz=$(sudo stat -c %s "$GROUP/$f")
if [ "$sz" -gt 200000 ]; then sudo rm -f "$GROUP/$f"; n=$((n+1)); fi
done
sudo sync
sudo sh -c ": > $MAP"
echo "removed $n container(s); index and small containers kept; map cleared"
restart "--learn=1" || exit 1
fprintd-list user 2>&1 | tail -1
echo "after:"; sizes
echo; echo "next: fplearn.sh enrol <finger>" ;;
enrol)
F=${2:-right-middle-finger}
if sudo grep -q "^$F " "$MAP" 2>/dev/null; then
echo "$F is already enrolled (in $MAP)."
echo "The trustlet REFUSES to re-enrol a finger it already holds; run"
echo " fplearn.sh wipe"
echo "first. (Measured 2026-09-04: 0 accepted of 7 presses, rc=0 each.)"
exit 1
fi
echo "=== enrol $F ==="
echo "sizes before:"; sizes
restart "--learn=1" || exit 1
/tmp/fpenrol.sh "$F"
echo; echo "sizes after the enrolment:"; sizes
echo; echo "next: fplearn.sh base" ;;
base)
N=${2:-15}; W=${3:-0}
echo "=== BASELINE: learning OFF ==="
restart "--learn=0" || exit 1
echo "sizes before:"; sizes
/tmp/fptrial.sh "$N" "$W"
echo; echo "sizes after (MUST be unchanged -- learning was off):"; sizes
echo; echo "next: fplearn.sh trend" ;;
trend)
# No wrong-finger taps by default: zero false accepts is settled over forty
# of them, and the presses are better spent on the false-negative rate.
N=${2:-15}; W=${3:-0}
echo "=== TREND: learning ON, three runs on one template ==="
restart "--learn=1" || exit 1
echo "sizes at the start:"; sizes
r=1
while [ $r -le 3 ]; do
echo; echo "----- learning run $r of 3 -----"
/tmp/fptrial.sh "$N" "$W"
echo "after run $r:"; sizes; folds
r=$((r+1))
done
echo
echo "Read it as a trend, not three numbers. A rate that climbs while the"
echo "container grows is learning; a rate that moves while the container"
echo "does not is noise, and the daemon's own 'learn:' lines say which." ;;
*)
sed -n '2,52p' "$0"; exit 1 ;;
esac