validity: implement emulation mode for enroll, verify, identify, list, delete, and clear storage

This commit is contained in:
Leonardo Francisco 2026-04-10 20:36:57 -04:00 committed by lewohart
parent 0f5f454c7c
commit 8a8f1f817f
5 changed files with 225 additions and 7 deletions

View file

@ -1217,6 +1217,10 @@ dev_open (FpDevice *device)
validity_sensor_state_init (&self->sensor);
validity_capture_state_init (&self->capture);
/* Emulation mode: in-memory print store for virtual sensor */
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
self->emulation_prints = g_ptr_array_new_with_free_func (g_object_unref);
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
{
fpi_device_open_complete (device, error);
@ -1255,6 +1259,8 @@ dev_close (FpDevice *device)
validity_pair_state_free (&self->pair_state);
validity_tls_free (&self->tls);
g_clear_pointer (&self->emulation_prints, g_ptr_array_unref);
g_clear_object (&self->interrupt_cancellable);
g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error);

View file

@ -303,6 +303,9 @@ struct _FpiDeviceValidity
/* Bulk data buffer (EP 0x82 reads during capture/calibration) */
guint8 *bulk_data;
gsize bulk_data_len;
/* Emulation mode: in-memory print storage (element-type FpPrint) */
GPtrArray *emulation_prints;
};
/* Enrollment SSM (validity_enroll.c) */

View file

@ -1553,13 +1553,51 @@ enroll_ssm_done (FpiSsm *ssm,
fpi_device_enroll_complete (dev, g_object_ref (print), NULL);
}
/* ================================================================
* Emulation mode: virtual enroll
* ================================================================ */
static void
emulation_enroll (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpPrint *print = NULL;
fpi_device_get_enroll_data (device, &print);
fpi_print_set_type (print, FPI_PRINT_RAW);
fpi_print_set_device_stored (print, TRUE);
/* Store serialisable data so the print survives serialisation */
g_autofree gchar *user_id = fpi_print_generate_user_id (print);
GVariant *data = g_variant_new_string (user_id);
g_object_set (print, "fpi-data", data, NULL);
/* Simulate progress for each enroll stage */
for (int i = 0; i < VALIDITY_ENROLL_STAGES - 1; i++)
fpi_device_enroll_progress (device, i, NULL, NULL);
/* Store in emulation memory */
g_ptr_array_add (self->emulation_prints, g_object_ref (print));
fpi_device_enroll_complete (device, g_object_ref (print), NULL);
}
void
validity_enroll (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpiSsm *ssm;
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_enroll (device);
return;
}
ssm = fpi_ssm_new (device, enroll_run_state, ENROLL_NUM_STATES);
fpi_ssm_start (ssm, enroll_ssm_done);
}

View file

@ -643,6 +643,88 @@ verify_ssm_done (FpiSsm *ssm,
self->bulk_data_len = 0;
}
/* ================================================================
* Emulation mode: virtual verify / identify / list / delete / clear
* ================================================================ */
static void
emulation_verify (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
if (self->emulation_prints->len > 0)
fpi_device_verify_report (device, FPI_MATCH_SUCCESS, NULL, NULL);
else
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL);
fpi_device_verify_complete (device, NULL);
}
static void
emulation_identify (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
GPtrArray *gallery = NULL;
fpi_device_get_identify_data (device, &gallery);
if (self->emulation_prints->len > 0 && gallery && gallery->len > 0)
fpi_device_identify_report (device,
g_ptr_array_index (gallery, 0),
g_ptr_array_index (self->emulation_prints, 0),
NULL);
else
fpi_device_identify_report (device, NULL, NULL, NULL);
fpi_device_identify_complete (device, NULL);
}
static void
emulation_list (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
GPtrArray *prints = g_ptr_array_new_with_free_func (g_object_unref);
for (guint i = 0; i < self->emulation_prints->len; i++)
g_ptr_array_add (prints,
g_object_ref (g_ptr_array_index (self->emulation_prints, i)));
fpi_device_list_complete (device, prints, NULL);
}
static void
emulation_delete (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpPrint *print = NULL;
fpi_device_get_delete_data (device, &print);
FpFinger target_finger = fp_print_get_finger (print);
for (guint i = 0; i < self->emulation_prints->len; i++)
{
FpPrint *stored = g_ptr_array_index (self->emulation_prints, i);
if (fp_print_get_finger (stored) == target_finger)
{
g_ptr_array_remove_index (self->emulation_prints, i);
break;
}
}
fpi_device_delete_complete (device, NULL);
}
static void
emulation_clear_storage (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
g_ptr_array_set_size (self->emulation_prints, 0);
fpi_device_clear_storage_complete (device, NULL);
}
void
validity_verify (FpDevice *device)
{
@ -651,6 +733,12 @@ validity_verify (FpDevice *device)
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_verify (device);
return;
}
self->identify_mode = FALSE;
ssm = fpi_ssm_new (device, verify_run_state, VERIFY_NUM_STATES);
@ -665,6 +753,12 @@ validity_identify (FpDevice *device)
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_identify (device);
return;
}
self->identify_mode = TRUE;
ssm = fpi_ssm_new (device, verify_run_state, VERIFY_NUM_STATES);
@ -837,11 +931,18 @@ list_ssm_done (FpiSsm *ssm,
void
validity_list (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpiSsm *ssm;
GPtrArray *prints_array;
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_list (device);
return;
}
prints_array = g_ptr_array_new_with_free_func (g_object_unref);
ssm = fpi_ssm_new (device, list_run_state, LIST_NUM_STATES);
@ -1048,10 +1149,17 @@ delete_ssm_done (FpiSsm *ssm,
void
validity_delete (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpiSsm *ssm;
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_delete (device);
return;
}
ssm = fpi_ssm_new (device, delete_run_state, DELETE_NUM_STATES);
fpi_ssm_start (ssm, delete_ssm_done);
}
@ -1173,10 +1281,17 @@ clear_ssm_done (FpiSsm *ssm,
void
validity_clear_storage (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
FpiSsm *ssm;
G_DEBUG_HERE ();
if (self->emulation_prints)
{
emulation_clear_storage (device);
return;
}
ssm = fpi_ssm_new (device, clear_run_state, CLEAR_NUM_STATES);
fpi_ssm_start (ssm, clear_ssm_done);
}

View file

@ -5,11 +5,13 @@ import sys
import gi
gi.require_version('FPrint', '2.0')
from gi.repository import FPrint
from gi.repository import FPrint, GLib
# Exit with error on any exception, including those happening in callbacks
sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))
ctx = GLib.main_context_default()
c = FPrint.Context()
c.enumerate()
devices = c.get_devices()
@ -23,7 +25,6 @@ del devices
assert d.get_driver() == "validity", f"Expected 'validity', got '{d.get_driver()}'"
# Verify features detected by auto_initialize_features
# Iteration 6 added enroll, verify, identify, list, delete, clear_storage.
assert not d.has_feature(FPrint.DeviceFeature.CAPTURE)
assert d.has_feature(FPrint.DeviceFeature.VERIFY)
assert d.has_feature(FPrint.DeviceFeature.IDENTIFY)
@ -34,14 +35,69 @@ assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE)
assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR)
assert d.has_feature(FPrint.DeviceFeature.ALWAYS_ON)
# Test open (sends GET_VERSION, cmd 0x19, GET_FW_INFO) and close
print("opening")
d.open_sync()
print("open done")
print("closing")
# 1. Clear storage — ensure the sensor is in a clean state
print("clearing storage")
d.clear_storage_sync()
print("clear done")
# 2. Enroll a finger
template = FPrint.Print.new(d)
def enroll_progress(*args):
print('enroll progress: ' + str(args))
print("enrolling")
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
p = d.enroll_sync(template, None, enroll_progress, None)
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
print("enroll done")
# 3. List enrolled prints — should have exactly one
print("listing")
stored = d.list_prints_sync()
print("listing done")
assert len(stored) == 1
# 4. Verify against the enrolled print
print("verifying")
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
verify_res, verify_print = d.verify_sync(p)
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
print("verify done")
del p
assert verify_res == True
# 5. Identify (async) with deserialized prints
identified = False
def identify_done(dev, res):
global identified
identified = True
identify_match, identify_print = dev.identify_finish(res)
print('identification done: ', identify_match, identify_print)
assert identify_match.equal(identify_print)
deserialized_prints = []
for p in stored:
deserialized_prints.append(FPrint.Print.deserialize(p.serialize()))
assert deserialized_prints[-1].equal(p)
del stored
print("async identifying")
d.identify(deserialized_prints, callback=identify_done)
del deserialized_prints
while not identified:
ctx.iteration(True)
# 6. Delete the enrolled print
print("deleting")
d.delete_print_sync(p)
print("delete done")
d.close_sync()
print("close done")
del d
del c