diff --git a/libfprint/drivers/validity/validity.c b/libfprint/drivers/validity/validity.c index b1ecb19c..da897b0c 100644 --- a/libfprint/drivers/validity/validity.c +++ b/libfprint/drivers/validity/validity.c @@ -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); diff --git a/libfprint/drivers/validity/validity.h b/libfprint/drivers/validity/validity.h index acbc12e9..3352ef75 100644 --- a/libfprint/drivers/validity/validity.h +++ b/libfprint/drivers/validity/validity.h @@ -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) */ diff --git a/libfprint/drivers/validity/validity_enroll.c b/libfprint/drivers/validity/validity_enroll.c index e291e014..a7e30a60 100644 --- a/libfprint/drivers/validity/validity_enroll.c +++ b/libfprint/drivers/validity/validity_enroll.c @@ -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); } diff --git a/libfprint/drivers/validity/validity_verify.c b/libfprint/drivers/validity/validity_verify.c index 54558ad2..4a74dd09 100644 --- a/libfprint/drivers/validity/validity_verify.c +++ b/libfprint/drivers/validity/validity_verify.c @@ -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); } diff --git a/tests/validity/custom.py b/tests/validity/custom.py index 03af0753..c9e4f370 100644 --- a/tests/validity/custom.py +++ b/tests/validity/custom.py @@ -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