From a033154b2eaae1cba6259fb414887a7865c8bb80 Mon Sep 17 00:00:00 2001 From: Matthew Mirvish Date: Mon, 15 Nov 2021 17:10:44 -0500 Subject: [PATCH 01/27] doc: Fix broken documentation for FpiDeviceUdevSubtypeFlags enum Added description and fixed incorrect name in comment, so now gtkdoc actually shows useful information. --- libfprint/fpi-device.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libfprint/fpi-device.h b/libfprint/fpi-device.h index 25e79c91..ab154bc1 100644 --- a/libfprint/fpi-device.h +++ b/libfprint/fpi-device.h @@ -25,9 +25,11 @@ #include "fpi-print.h" /** - * FpiDeviceUdevSubtype: + * FpiDeviceUdevSubtypeFlags: * @FPI_DEVICE_UDEV_SUBTYPE_SPIDEV: The device requires an spidev node * @FPI_DEVICE_UDEV_SUBTYPE_HIDRAW: The device requires a hidraw node + * + * Bitfield of required hardware resources for a udev-backed device. */ typedef enum { FPI_DEVICE_UDEV_SUBTYPE_SPIDEV = 1 << 0, From 05fd2c58cb9965b60e3b39de93e06abfb4f286da Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 24 Nov 2021 12:09:18 +0100 Subject: [PATCH 02/27] context: Ensure mainloop is idle before enumeration completes This ensures that we have processed all hotplug events before considering enumeration to be complete. This is important due to USB persist being turned off. At resume time, devices will disappear and immediately re-appear. In this situatoin, enumerate could first see the old state with a removed device resulting in it to not be discovered. As a hotplug event is semingly emitted by the kernel immediately, we can simply make sure to process this hotplug event before returning from enumerate. Closes: fprintd#119 --- libfprint/fp-context.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libfprint/fp-context.c b/libfprint/fp-context.c index d4bd0000..1f322157 100644 --- a/libfprint/fp-context.c +++ b/libfprint/fp-context.c @@ -426,6 +426,7 @@ void fp_context_enumerate (FpContext *context) { FpContextPrivate *priv = fp_context_get_instance_private (context); + gboolean dispatched; gint i; g_return_if_fail (FP_IS_CONTEXT (context)); @@ -564,8 +565,19 @@ fp_context_enumerate (FpContext *context) } #endif - while (priv->pending_devices) - g_main_context_iteration (NULL, TRUE); + /* Iterate until 1. we have no pending devices, and 2. the mainloop is idle + * This takes care of processing hotplug events that happened during + * enumeration. + * This is important due to USB `persist` being turned off. At resume time, + * devices will disappear and immediately re-appear. In this situation, + * enumerate could first see the old state with a removed device resulting + * in it to not be discovered. + * As a hotplug event is seemingly emitted by the kernel immediately, we can + * simply make sure to process all events before returning from enumerate. + */ + dispatched = TRUE; + while (priv->pending_devices || dispatched) + dispatched = g_main_context_iteration (NULL, !!priv->pending_devices); } /** From 31afd3ba5c24974c6030220154a8f93dd85429c2 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 2 Dec 2021 13:28:12 +0100 Subject: [PATCH 03/27] elanspi: Move debug print so that it contains all information Two of the printed variables were only calculated after the message was printed, making the logged information less useful than it could be. --- libfprint/drivers/elanspi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libfprint/drivers/elanspi.c b/libfprint/drivers/elanspi.c index daea67c3..6ecd21ba 100644 --- a/libfprint/drivers/elanspi.c +++ b/libfprint/drivers/elanspi.c @@ -1219,8 +1219,6 @@ elanspi_guess_image (FpiDeviceElanSpi *self, guint16 *raw_image) sq_stddev /= (frame_width * frame_height); - fp_dbg (" stddev=%" G_GUINT64_FORMAT "d, ip=%d, is_fp=%d, is_empty=%d", sq_stddev, invalid_percent, is_fp, is_empty); - if (invalid_percent < ELANSPI_MAX_REAL_INVALID_PERCENT) is_fp += 1; if (invalid_percent > ELANSPI_MIN_EMPTY_INVALID_PERCENT) @@ -1231,6 +1229,8 @@ elanspi_guess_image (FpiDeviceElanSpi *self, guint16 *raw_image) if (sq_stddev < ELANSPI_MAX_EMPTY_STDDEV) is_empty += 1; + fp_dbg (" stddev=%" G_GUINT64_FORMAT "d, ip=%d, is_fp=%d, is_empty=%d", sq_stddev, invalid_percent, is_fp, is_empty); + if (is_fp > is_empty) return ELANSPI_GUESS_FINGERPRINT; else if (is_empty > is_fp) From 3981c42d3e26e3cbae808533963d14910b427ca5 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 2 Dec 2021 13:44:57 +0100 Subject: [PATCH 04/27] ssm: Add API to silence most state change debug messages Otherwise tightly looping SSMs (primarily SPI transfers), will flood the logs in inappropriate ways. --- doc/libfprint-2-sections.txt | 1 + libfprint/fpi-ssm.c | 32 +++++++++++++++++++++++++------- libfprint/fpi-ssm.h | 2 ++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/doc/libfprint-2-sections.txt b/doc/libfprint-2-sections.txt index 91258b62..0fb0cfab 100644 --- a/doc/libfprint-2-sections.txt +++ b/doc/libfprint-2-sections.txt @@ -260,6 +260,7 @@ fpi_ssm_get_device fpi_ssm_get_error fpi_ssm_dup_error fpi_ssm_get_cur_state +fpi_ssm_silence_debug fpi_ssm_spi_transfer_cb fpi_ssm_spi_transfer_with_weak_pointer_cb fpi_ssm_usb_transfer_cb diff --git a/libfprint/fpi-ssm.c b/libfprint/fpi-ssm.c index 8ceab674..c34498ab 100644 --- a/libfprint/fpi-ssm.c +++ b/libfprint/fpi-ssm.c @@ -81,6 +81,7 @@ struct _FpiSsm int start_cleanup; int cur_state; gboolean completed; + gboolean silence; GSource *timeout; GError *error; FpiSsmCompletedCallback callback; @@ -245,10 +246,11 @@ fpi_ssm_free (FpiSsm *machine) /* Invoke the state handler */ static void -__ssm_call_handler (FpiSsm *machine) +__ssm_call_handler (FpiSsm *machine, gboolean force_msg) { - fp_dbg ("[%s] %s entering state %d", fp_device_get_driver (machine->dev), - machine->name, machine->cur_state); + if (force_msg || !machine->silence) + fp_dbg ("[%s] %s entering state %d", fp_device_get_driver (machine->dev), + machine->name, machine->cur_state); machine->handler (machine, machine->dev); } @@ -275,7 +277,7 @@ fpi_ssm_start (FpiSsm *ssm, FpiSsmCompletedCallback callback) ssm->cur_state = 0; ssm->completed = FALSE; ssm->error = NULL; - __ssm_call_handler (ssm); + __ssm_call_handler (ssm, TRUE); } static void @@ -346,7 +348,7 @@ fpi_ssm_mark_completed (FpiSsm *machine) if (next_state < machine->nr_states) { machine->cur_state = next_state; - __ssm_call_handler (machine); + __ssm_call_handler (machine, TRUE); return; } @@ -460,7 +462,7 @@ fpi_ssm_next_state (FpiSsm *machine) if (machine->cur_state == machine->nr_states) fpi_ssm_mark_completed (machine); else - __ssm_call_handler (machine); + __ssm_call_handler (machine, FALSE); } void @@ -537,7 +539,7 @@ fpi_ssm_jump_to_state (FpiSsm *machine, int state) if (machine->cur_state == machine->nr_states) fpi_ssm_mark_completed (machine); else - __ssm_call_handler (machine); + __ssm_call_handler (machine, FALSE); } typedef struct @@ -642,6 +644,22 @@ fpi_ssm_dup_error (FpiSsm *machine) return NULL; } +/** + * fpi_ssm_silence_debug: + * @machine: an #FpiSsm state machine + * + * Turn off state change debug messages from this SSM. This does not disable + * all messages, as e.g. the initial state, SSM completion and cleanup states + * are still printed out. + * + * Use if the SSM loops and would flood the debug log otherwise. + */ +void +fpi_ssm_silence_debug (FpiSsm *machine) +{ + machine->silence = TRUE; +} + /** * fpi_ssm_usb_transfer_cb: * @transfer: a #FpiUsbTransfer diff --git a/libfprint/fpi-ssm.h b/libfprint/fpi-ssm.h index 235e84ad..d2601c88 100644 --- a/libfprint/fpi-ssm.h +++ b/libfprint/fpi-ssm.h @@ -96,6 +96,8 @@ GError * fpi_ssm_get_error (FpiSsm *machine); GError * fpi_ssm_dup_error (FpiSsm *machine); int fpi_ssm_get_cur_state (FpiSsm *machine); +void fpi_ssm_silence_debug (FpiSsm *machine); + /* Callbacks to be used by the driver instead of implementing their own * logic. */ From e198b0422220f14831de799e7a9bf7f0696df95f Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 2 Dec 2021 13:46:07 +0100 Subject: [PATCH 05/27] elanspi: Silence some SSMs that may log excessively otherwise --- libfprint/drivers/elanspi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libfprint/drivers/elanspi.c b/libfprint/drivers/elanspi.c index 6ecd21ba..c3ddea9b 100644 --- a/libfprint/drivers/elanspi.c +++ b/libfprint/drivers/elanspi.c @@ -606,6 +606,7 @@ elanspi_calibrate_old_handler (FpiSsm *ssm, FpDevice *dev) case ELANSPI_CALIBOLD_CHECKFIN_CAPTURE: case ELANSPI_CALIBOLD_DACFINE_CAPTURE: chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES); + fpi_ssm_silence_debug (chld); fpi_ssm_start_subsm (ssm, chld); return; @@ -860,6 +861,7 @@ elanspi_calibrate_hv_handler (FpiSsm *ssm, FpDevice *dev) case ELANSPI_CALIBHV_CAPTURE: chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES); + fpi_ssm_silence_debug (chld); fpi_ssm_start_subsm (ssm, chld); return; @@ -1115,6 +1117,7 @@ do_sw_reset: chld = fpi_ssm_new_full (dev, elanspi_calibrate_hv_handler, ELANSPI_CALIBHV_NSTATES, ELANSPI_CALIBHV_PROTECT, "HV calibrate"); else chld = fpi_ssm_new_full (dev, elanspi_calibrate_old_handler, ELANSPI_CALIBOLD_NSTATES, ELANSPI_CALIBOLD_PROTECT, "old calibrate"); + fpi_ssm_silence_debug (chld); fpi_ssm_start_subsm (ssm, chld); return; @@ -1123,6 +1126,7 @@ do_sw_reset: chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES); else chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES); + fpi_ssm_silence_debug (chld); fpi_ssm_start_subsm (ssm, chld); return; @@ -1495,6 +1499,7 @@ elanspi_fp_capture_ssm_handler (FpiSsm *ssm, FpDevice *dev) chld = fpi_ssm_new (dev, elanspi_capture_hv_handler, ELANSPI_CAPTHV_NSTATES); else chld = fpi_ssm_new (dev, elanspi_capture_old_handler, ELANSPI_CAPTOLD_NSTATES); + fpi_ssm_silence_debug (chld); fpi_ssm_start_subsm (ssm, chld); return; From 999bca076cd69a6ae4df6dd668bcf45a6565fc44 Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Sun, 29 Aug 2021 14:18:29 +0300 Subject: [PATCH 06/27] Allow FpPrint data to be extended on enrollment. * Allow FPI_PRINT_NBIS to be extended rather than overridden if a user supplies an existing FpPrint template with data; * Prints will only be extended if a device has the required feature. For image-based devices this feature is added by default since they typically do not have storage (this can be overridden at the child class level). Extending an existing FpPrint requires passing a template print with existing data during the enrollment process. This is done because the caller is responsible for maintaining the fingerprint database and doing the necessary deserialization operations if needed. The existing example program is updated to show how to do that. --- examples/enroll.c | 63 +++++++++++++++--- examples/storage.c | 54 ++++++++++------ examples/storage.h | 8 ++- libfprint/fp-device.c | 32 +++++++--- libfprint/fp-device.h | 2 + libfprint/fp-image-device.c | 6 +- libfprint/fp-print.c | 23 +++++++ tests/test-fpi-device.c | 124 +++++++++++++++++++++++++++++++++++- tests/virtual-image.py | 45 ++++++++++--- 9 files changed, 305 insertions(+), 52 deletions(-) diff --git a/examples/enroll.c b/examples/enroll.c index f133c8b8..10dfa5da 100644 --- a/examples/enroll.c +++ b/examples/enroll.c @@ -35,6 +35,7 @@ typedef struct _EnrollData unsigned int sigint_handler; FpFinger finger; int ret_value; + gboolean update_fingerprint; } EnrollData; static void @@ -84,7 +85,8 @@ on_enroll_completed (FpDevice *dev, GAsyncResult *res, void *user_data) /* Even if the device has storage, it may not be able to save all the * metadata that the print contains, so we can always save a local copy * containing the handle to the device print */ - int r = print_data_save (print, enroll_data->finger); + int r = print_data_save (print, enroll_data->finger, + enroll_data->update_fingerprint); if (r < 0) { g_warning ("Data save failed, code %d", r); @@ -124,6 +126,40 @@ on_enroll_progress (FpDevice *device, fp_device_get_nr_enroll_stages (device)); } +static gboolean +should_update_fingerprint (void) +{ + int update_choice; + gboolean update_fingerprint = FALSE; + + printf ("Should an existing fingerprint be updated instead of being replaced (if present)? " + "Enter Y/y or N/n to make a choice.\n"); + update_choice = getchar (); + if (update_choice == EOF) + { + g_warning ("EOF encountered while reading a character"); + return EXIT_FAILURE; + } + + switch (update_choice) + { + case 'y': + case 'Y': + update_fingerprint = TRUE; + break; + + case 'n': + case 'N': + update_fingerprint = FALSE; + break; + + default: + g_warning ("Invalid choice %c, should be Y/y or N/n.", update_choice); + return EXIT_FAILURE; + } + return update_fingerprint; +} + static void on_device_opened (FpDevice *dev, GAsyncResult *res, void *user_data) { @@ -139,13 +175,26 @@ on_device_opened (FpDevice *dev, GAsyncResult *res, void *user_data) return; } - printf ("Opened device. It's now time to enroll your finger.\n\n"); + printf ("Opened device.\n"); + + if (fp_device_has_feature (dev, FP_DEVICE_FEATURE_UPDATE_PRINT)) + { + printf ("The device supports fingerprint updates.\n"); + enroll_data->update_fingerprint = should_update_fingerprint (); + } + else + { + printf ("The device doesn't support fingerprint updates. Old prints will be erased.\n"); + enroll_data->update_fingerprint = FALSE; + } + + printf ("It's now time to enroll your finger.\n\n"); printf ("You will need to successfully scan your %s finger %d times to " "complete the process.\n\n", finger_to_string (enroll_data->finger), fp_device_get_nr_enroll_stages (dev)); printf ("Scan your finger now.\n"); - print_template = print_create_template (dev, enroll_data->finger); + print_template = print_create_template (dev, enroll_data->finger, enroll_data->update_fingerprint); fp_device_enroll (dev, print_template, enroll_data->cancellable, on_enroll_progress, NULL, NULL, (GAsyncReadyCallback) on_enroll_completed, @@ -171,11 +220,9 @@ main (void) FpDevice *dev; FpFinger finger; - g_print ("This program will enroll the selected finger, unconditionally " - "overwriting any print for the same finger that was enrolled " - "previously. If you want to continue, press enter, otherwise hit " - "Ctrl+C\n"); - getchar (); + g_print ("This program will enroll the selected finger overwriting any print for the same" + " finger that was enrolled previously. Fingerprint updates without erasing old data" + " are possible on devices supporting that. Ctrl+C interrupts program execution.\n"); g_print ("Choose the finger to enroll:\n"); finger = finger_chooser (); diff --git a/examples/storage.c b/examples/storage.c index 6c92dba6..b84c72d5 100644 --- a/examples/storage.c +++ b/examples/storage.c @@ -102,8 +102,23 @@ save_data (GVariant *data) return 0; } +static FpPrint * +load_print_from_data (GVariant *data) +{ + const guchar *stored_data = NULL; + gsize stored_len; + FpPrint *print; + + g_autoptr(GError) error = NULL; + stored_data = (const guchar *) g_variant_get_fixed_array (data, &stored_len, 1); + print = fp_print_deserialize (stored_data, stored_len, &error); + if (error) + g_warning ("Error deserializing data: %s", error->message); + return print; +} + int -print_data_save (FpPrint *print, FpFinger finger) +print_data_save (FpPrint *print, FpFinger finger, gboolean update_fingerprint) { g_autofree gchar *descr = get_print_data_descriptor (print, NULL, finger); @@ -137,25 +152,12 @@ print_data_load (FpDevice *dev, FpFinger finger) g_autoptr(GVariant) val = NULL; g_autoptr(GVariantDict) dict = NULL; - const guchar *stored_data = NULL; - gsize stored_len; dict = load_data (); val = g_variant_dict_lookup_value (dict, descr, G_VARIANT_TYPE ("ay")); if (val) - { - FpPrint *print; - g_autoptr(GError) error = NULL; - - stored_data = (const guchar *) g_variant_get_fixed_array (val, &stored_len, 1); - print = fp_print_deserialize (stored_data, stored_len, &error); - - if (error) - g_warning ("Error deserializing data: %s", error->message); - - return print; - } + return load_print_from_data (val); return NULL; } @@ -207,16 +209,30 @@ gallery_data_load (FpDevice *dev) } FpPrint * -print_create_template (FpDevice *dev, FpFinger finger) +print_create_template (FpDevice *dev, FpFinger finger, gboolean load_existing) { + g_autoptr(GVariantDict) dict = NULL; g_autoptr(GDateTime) datetime = NULL; g_autoptr(GDate) date = NULL; + g_autoptr(GVariant) existing_val = NULL; + g_autofree gchar *descr = get_print_data_descriptor (NULL, dev, finger); FpPrint *template = NULL; gint year, month, day; - template = fp_print_new (dev); - fp_print_set_finger (template, finger); - fp_print_set_username (template, g_get_user_name ()); + if (load_existing) + { + dict = load_data (); + existing_val = g_variant_dict_lookup_value (dict, descr, G_VARIANT_TYPE ("ay")); + if (existing_val != NULL) + template = load_print_from_data (existing_val); + } + if (template == NULL) + { + template = fp_print_new (dev); + fp_print_set_finger (template, finger); + fp_print_set_username (template, g_get_user_name ()); + } + datetime = g_date_time_new_now_local (); g_date_time_get_ymd (datetime, &year, &month, &day); date = g_date_new_dmy (day, month, year); diff --git a/examples/storage.h b/examples/storage.h index 81390bd0..0922b902 100644 --- a/examples/storage.h +++ b/examples/storage.h @@ -21,12 +21,14 @@ #pragma once int print_data_save (FpPrint *print, - FpFinger finger); + FpFinger finger, + gboolean update_fingerprint); FpPrint * print_data_load (FpDevice *dev, FpFinger finger); GPtrArray * gallery_data_load (FpDevice *dev); -FpPrint * print_create_template (FpDevice *dev, - FpFinger finger); +FpPrint * print_create_template (FpDevice *dev, + FpFinger finger, + const gboolean load_existing); gboolean print_image_save (FpPrint *print, const char *path); gboolean save_image_to_pgm (FpImage *img, diff --git a/libfprint/fp-device.c b/libfprint/fp-device.c index 35e2f2b8..82f309e8 100644 --- a/libfprint/fp-device.c +++ b/libfprint/fp-device.c @@ -1189,10 +1189,11 @@ fp_device_resume_finish (FpDevice *device, * fp_device_enroll_finish(). * * The @template_print parameter is a #FpPrint with available metadata filled - * in. The driver may make use of this metadata, when e.g. storing the print on - * device memory. It is undefined whether this print is filled in by the driver - * and returned, or whether the driver will return a newly created print after - * enrollment succeeded. + * in and, optionally, with existing fingerprint data to be updated with newly + * enrolled fingerprints if a device driver supports it. The driver may make use + * of the metadata, when e.g. storing the print on device memory. It is undefined + * whether this print is filled in by the driver and returned, or whether the + * driver will return a newly created print after enrollment succeeded. */ void fp_device_enroll (FpDevice *device, @@ -1229,19 +1230,30 @@ fp_device_enroll (FpDevice *device, if (!FP_IS_PRINT (template_print)) { - g_warning ("User did not pass a print template!"); g_task_return_error (task, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "User did not pass a print template!")); return; } g_object_get (template_print, "fpi-type", &print_type, NULL); if (print_type != FPI_PRINT_UNDEFINED) { - g_warning ("Passed print template must be newly created and blank!"); - g_task_return_error (task, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); - return; + if (!fp_device_has_feature (device, FP_DEVICE_FEATURE_UPDATE_PRINT)) + { + g_task_return_error (task, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "A device does not support print updates!")); + return; + } + if (!fp_print_compatible (template_print, device)) + { + g_task_return_error (task, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "The print and device must have a matching driver and device id" + " for a fingerprint update to succeed")); + return; + } } priv->current_action = FPI_DEVICE_ACTION_ENROLL; diff --git a/libfprint/fp-device.h b/libfprint/fp-device.h index 85be34c4..9dda747c 100644 --- a/libfprint/fp-device.h +++ b/libfprint/fp-device.h @@ -59,6 +59,7 @@ typedef enum { * @FP_DEVICE_FEATURE_STORAGE_CLEAR: Supports clearing the whole storage * @FP_DEVICE_FEATURE_DUPLICATES_CHECK: Natively supports duplicates detection * @FP_DEVICE_FEATURE_ALWAYS_ON: Whether the device can run continuously + * @FP_DEVICE_FEATURE_UPDATE_PRINT: Supports updating an existing print record using new scans */ typedef enum /*< flags >*/ { FP_DEVICE_FEATURE_NONE = 0, @@ -71,6 +72,7 @@ typedef enum /*< flags >*/ { FP_DEVICE_FEATURE_STORAGE_CLEAR = 1 << 6, FP_DEVICE_FEATURE_DUPLICATES_CHECK = 1 << 7, FP_DEVICE_FEATURE_ALWAYS_ON = 1 << 8, + FP_DEVICE_FEATURE_UPDATE_PRINT = 1 << 9, } FpDeviceFeature; /** diff --git a/libfprint/fp-image-device.c b/libfprint/fp-image-device.c index 82e69f99..519ad754 100644 --- a/libfprint/fp-image-device.c +++ b/libfprint/fp-image-device.c @@ -101,6 +101,7 @@ fp_image_device_start_capture_action (FpDevice *device) FpImageDevice *self = FP_IMAGE_DEVICE (device); FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self); FpiDeviceAction action; + FpiPrintType print_type; /* There is just one action that we cannot support out * of the box, which is a capture without first waiting @@ -124,7 +125,9 @@ fp_image_device_start_capture_action (FpDevice *device) FpPrint *enroll_print = NULL; fpi_device_get_enroll_data (device, &enroll_print); - fpi_print_set_type (enroll_print, FPI_PRINT_NBIS); + g_object_get (enroll_print, "fpi-type", &print_type, NULL); + if (print_type != FPI_PRINT_NBIS) + fpi_print_set_type (enroll_print, FPI_PRINT_NBIS); } priv->enroll_stage = 0; @@ -221,6 +224,7 @@ fp_image_device_class_init (FpImageDeviceClass *klass) fp_device_class->cancel = fp_image_device_cancel_action; fpi_device_class_auto_initialize_features (fp_device_class); + fp_device_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT; /* Default implementations */ klass->activate = fp_image_device_default_activate; diff --git a/libfprint/fp-print.c b/libfprint/fp-print.c index c8a1b071..8532b6cc 100644 --- a/libfprint/fp-print.c +++ b/libfprint/fp-print.c @@ -61,6 +61,7 @@ enum { /* Private property*/ PROP_FPI_TYPE, PROP_FPI_DATA, + PROP_FPI_PRINTS, N_PROPS }; @@ -133,6 +134,10 @@ fp_print_get_property (GObject *object, g_value_set_variant (value, self->data); break; + case PROP_FPI_PRINTS: + g_value_set_pointer (value, self->prints); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -188,6 +193,11 @@ fp_print_set_property (GObject *object, self->data = g_value_dup_variant (value); break; + case PROP_FPI_PRINTS: + g_clear_pointer (&self->prints, g_ptr_array_unref); + self->prints = g_value_get_pointer (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -299,6 +309,19 @@ fp_print_class_init (FpPrintClass *klass) NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + /** + * FpPrint::fpi-prints: (skip) + * + * This property is only for internal purposes. + * + * Stability: private + */ + properties[PROP_FPI_PRINTS] = + g_param_spec_pointer ("fpi-prints", + "Prints", + "Prints for internal use only", + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE); + g_object_class_install_properties (object_class, N_PROPS, properties); } diff --git a/tests/test-fpi-device.c b/tests/test-fpi-device.c index 408e2f92..ff658e97 100644 --- a/tests/test-fpi-device.c +++ b/tests/test-fpi-device.c @@ -27,6 +27,7 @@ #include "fpi-compat.h" #include "fpi-log.h" #include "test-device-fake.h" +#include "fp-print-private.h" /* Utility functions */ @@ -142,6 +143,16 @@ make_fake_print (FpDevice *device, return enrolled_print; } +static FpPrint * +make_fake_nbis_print (FpDevice *device) +{ + FpPrint *enrolled_print = fp_print_new (device); + + fpi_print_set_type (enrolled_print, FPI_PRINT_NBIS); + + return enrolled_print; +} + static FpPrint * make_fake_print_reffed (FpDevice *device, GVariant *print_data) @@ -1045,7 +1056,6 @@ test_driver_enroll_error_no_print (void) out_print = fp_device_enroll_sync (device, fp_print_new (device), NULL, NULL, NULL, &error); - g_test_assert_expected_messages (); g_assert (fake_dev->last_called_function == dev_class->enroll); g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_GENERAL); g_assert_null (out_print); @@ -1053,6 +1063,111 @@ test_driver_enroll_error_no_print (void) g_clear_error (&error); } +static void +test_driver_enroll_update_nbis (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) template_print = NULL; + FpiDeviceFake *fake_dev = NULL; + FpPrint *out_print = NULL; + + dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT; + device = auto_close_fake_device_new (); + fake_dev = FPI_DEVICE_FAKE (device); + + template_print = make_fake_nbis_print (device); + fake_dev->ret_print = template_print; + + out_print = + fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error); + + g_assert (fake_dev->last_called_function == dev_class->enroll); + g_assert (fake_dev->action_data == template_print); + + g_assert_no_error (error); + g_assert (out_print == template_print); +} + +static void +test_driver_enroll_update_nbis_wrong_device (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) template_print = NULL; + FpiDeviceFake *fake_dev = NULL; + FpPrint *out_print = NULL; + + dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT; + + device = auto_close_fake_device_new (); + fake_dev = FPI_DEVICE_FAKE (device); + + template_print = make_fake_nbis_print (device); + template_print->device_id = g_strdup ("wrong_device"); + fake_dev->ret_print = template_print; + + out_print = + fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_assert (out_print == NULL); +} + +static void +test_driver_enroll_update_nbis_wrong_driver (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) template_print = NULL; + FpiDeviceFake *fake_dev = NULL; + FpPrint *out_print = NULL; + + dev_class->features |= FP_DEVICE_FEATURE_UPDATE_PRINT; + + device = auto_close_fake_device_new (); + fake_dev = FPI_DEVICE_FAKE (device); + + template_print = make_fake_nbis_print (device); + template_print->driver = g_strdup ("wrong_driver"); + fake_dev->ret_print = template_print; + + out_print = + fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_assert (out_print == NULL); +} + +static void +test_driver_enroll_update_nbis_missing_feature (void) +{ + g_autoptr(GError) error = NULL; + g_autoptr(FpAutoResetClass) dev_class = auto_reset_device_class (); + g_autoptr(FpAutoCloseDevice) device = NULL; + g_autoptr(FpPrint) template_print = NULL; + FpiDeviceFake *fake_dev = NULL; + FpPrint *out_print = NULL; + + device = auto_close_fake_device_new (); + fake_dev = FPI_DEVICE_FAKE (device); + + template_print = make_fake_nbis_print (device); + fake_dev->ret_print = template_print; + + out_print = + fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error); + + g_assert (fake_dev->last_called_function == dev_class->open); + g_assert (fake_dev->action_data == NULL); + + g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID); + g_assert (out_print == NULL); +} + typedef struct { gint completed_stages; @@ -3306,6 +3421,13 @@ main (int argc, char *argv[]) g_test_add_func ("/driver/enroll/error", test_driver_enroll_error); g_test_add_func ("/driver/enroll/error/no_print", test_driver_enroll_error_no_print); g_test_add_func ("/driver/enroll/progress", test_driver_enroll_progress); + g_test_add_func ("/driver/enroll/update_nbis", test_driver_enroll_update_nbis); + g_test_add_func ("/driver/enroll/update_nbis_wrong_device", + test_driver_enroll_update_nbis_wrong_device); + g_test_add_func ("/driver/enroll/update_nbis_wrong_driver", + test_driver_enroll_update_nbis_wrong_driver); + g_test_add_func ("/driver/enroll/update_nbis_missing_feature", + test_driver_enroll_update_nbis_missing_feature); g_test_add_func ("/driver/verify", test_driver_verify); g_test_add_func ("/driver/verify/fail", test_driver_verify_fail); g_test_add_func ("/driver/verify/retry", test_driver_verify_retry); diff --git a/tests/virtual-image.py b/tests/virtual-image.py index 7605b583..e4a464eb 100755 --- a/tests/virtual-image.py +++ b/tests/virtual-image.py @@ -136,6 +136,7 @@ class VirtualImage(unittest.TestCase): self.assertTrue(self.dev.has_feature(FPrint.DeviceFeature.CAPTURE)) self.assertTrue(self.dev.has_feature(FPrint.DeviceFeature.IDENTIFY)) self.assertTrue(self.dev.has_feature(FPrint.DeviceFeature.VERIFY)) + self.assertTrue(self.dev.has_feature(FPrint.DeviceFeature.UPDATE_PRINT)) self.assertFalse(self.dev.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK)) self.assertFalse(self.dev.has_feature(FPrint.DeviceFeature.STORAGE)) self.assertFalse(self.dev.has_feature(FPrint.DeviceFeature.STORAGE_LIST)) @@ -144,7 +145,8 @@ class VirtualImage(unittest.TestCase): self.assertEqual(self.dev.get_features(), FPrint.DeviceFeature.CAPTURE | FPrint.DeviceFeature.IDENTIFY | - FPrint.DeviceFeature.VERIFY) + FPrint.DeviceFeature.VERIFY | + FPrint.DeviceFeature.UPDATE_PRINT) def test_capture_prevents_close(self): cancel = Gio.Cancellable() @@ -167,7 +169,7 @@ class VirtualImage(unittest.TestCase): while not self._cancelled: ctx.iteration(True) - def enroll_print(self, image): + def enroll_print(self, image, template=None): self._step = 0 self._enrolled = None @@ -181,14 +183,15 @@ class VirtualImage(unittest.TestCase): self.assertEqual(self.dev.get_finger_status(), FPrint.FingerStatusFlags.NONE) self._enrolled = fp - template = FPrint.Print.new(self.dev) - template.props.finger = FPrint.Finger.LEFT_THUMB - template.props.username = "testuser" - template.props.description = "test print" - datetime = GLib.DateTime.new_now_local() - date = GLib.Date() - date.set_dmy(*datetime.get_ymd()[::-1]) - template.props.enroll_date = date + if template is None: + template = FPrint.Print.new(self.dev) + template.props.finger = FPrint.Finger.LEFT_THUMB + template.props.username = "testuser" + template.props.description = "test print" + datetime = GLib.DateTime.new_now_local() + date = GLib.Date() + date.set_dmy(*datetime.get_ymd()[::-1]) + template.props.enroll_date = date self.assertEqual(self.dev.get_finger_status(), FPrint.FingerStatusFlags.NONE) self.dev.enroll(template, None, progress_cb, tuple(), done_cb) @@ -264,6 +267,28 @@ class VirtualImage(unittest.TestCase): ctx.iteration(True) assert(not self._verify_match) + # Test fingerprint updates + # Enroll a second print + fp_whorl_tended_arch = self.enroll_print('tented_arch', fp_whorl) + + # Make sure the first print verifies successfully after the update + self._verify_match = None + self._verify_fp = None + self.dev.verify(fp_whorl_tended_arch, callback=verify_cb) + self.send_image('whorl') + while self._verify_match is None: + ctx.iteration(True) + assert(self._verify_match) + + # Make sure the second print verifies successfully after the update + self._verify_match = None + self._verify_fp = None + self.dev.verify(fp_whorl_tended_arch, callback=verify_cb) + self.send_image('tented_arch') + while self._verify_match is None: + ctx.iteration(True) + assert(self._verify_match) + # Test verify error cases self._verify_fp = None self._verify_error = None From 7565562903544f22d4eb0688330a6efd43f04c05 Mon Sep 17 00:00:00 2001 From: mincrmatt12 Date: Tue, 21 Dec 2021 03:19:01 -0500 Subject: [PATCH 07/27] elanspi: Adjust register tables (fixes #438) New values taken from a newer version of the official driver. --- libfprint/drivers/elanspi.h | 38 +++++++++++++++++++++++++++++++++---- tests/elanspi/capture.ioctl | 7 +++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/libfprint/drivers/elanspi.h b/libfprint/drivers/elanspi.h index c9e17b8c..ffe34160 100644 --- a/libfprint/drivers/elanspi.h +++ b/libfprint/drivers/elanspi.h @@ -97,7 +97,37 @@ static const struct elanspi_reg_entry elanspi_calibration_table_default[] = { {0xff, 0xff} }; -static const struct elanspi_reg_entry elanspi_calibration_table_id567[] = { +static const struct elanspi_reg_entry elanspi_calibration_table_id6[] = { + {0x2A, 0x07}, + {0x1, 0x00}, + {0x2, 0x5f}, + {0x3, 0x00}, + {0x4, 0x5f}, + {0x5, 0x60}, + {0x6, 0xC0}, + {0x7, 0x80}, + {0x8, 0x04}, + {0xA, 0x97}, + {0xB, 0x72}, + {0xC, 0x69}, + {0xF, 0x2A}, + {0x11, 0x2A}, + {0x13, 0x27}, + {0x15, 0x67}, + {0x18, 0x04}, + {0x21, 0x20}, + {0x22, 0x36}, + {0x29, 0x02}, + {0x2A, 0x03}, + {0x2A, 0x5F}, + {0x2B, 0xC0}, + {0x2C, 0x10}, + {0x2E, 0xFF}, + + {0xff, 0xff} +}; + +static const struct elanspi_reg_entry elanspi_calibration_table_id57[] = { {0x2A, 0x07}, {0x5, 0x60}, {0x6, 0xC0}, @@ -143,9 +173,9 @@ static const struct elanspi_regtable elanspi_calibration_table_old = { .other = elanspi_calibration_table_default, .entries = { { .sid = 0x0, .table = elanspi_calibration_table_id0 }, - { .sid = 0x5, .table = elanspi_calibration_table_id567 }, - { .sid = 0x6, .table = elanspi_calibration_table_id567 }, - { .sid = 0x7, .table = elanspi_calibration_table_id567 }, + { .sid = 0x5, .table = elanspi_calibration_table_id57 }, + { .sid = 0x6, .table = elanspi_calibration_table_id6 }, + { .sid = 0x7, .table = elanspi_calibration_table_id57 }, { .sid = 0x0, .table = NULL } } }; diff --git a/tests/elanspi/capture.ioctl b/tests/elanspi/capture.ioctl index dcd5821e..fc292d36 100644 --- a/tests/elanspi/capture.ioctl +++ b/tests/elanspi/capture.ioctl @@ -24,6 +24,10 @@ TW 8c62 TW 805a TW 04 TW aa07 +TW 8100 +TW 825f +TW 8300 +TW 845f TW 8560 TW 86c0 TW 8780 @@ -38,8 +42,11 @@ TW 9567 TW 9804 TW a120 TW a236 +TW a902 +TW aa03 TW aa5f TW abc0 +TW ac10 TW aeff TW 01 TW 03ff From 5beac0ded7992b107893a155f5b6b0362d7faf11 Mon Sep 17 00:00:00 2001 From: mincrmatt12 Date: Wed, 22 Dec 2021 20:35:30 -0500 Subject: [PATCH 08/27] elanspi: Try to avoid cancellation problems --- libfprint/drivers/elanspi.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/elanspi.c b/libfprint/drivers/elanspi.c index c3ddea9b..9338013d 100644 --- a/libfprint/drivers/elanspi.c +++ b/libfprint/drivers/elanspi.c @@ -439,6 +439,12 @@ elanspi_capture_old_line_handler (FpiSpiTransfer *transfer, FpDevice *dev, gpoin } else { + /* check for termination */ + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_NONE) + { + fpi_ssm_mark_completed (transfer->ssm); + return; + } /* check for cancellation */ if (fpi_device_action_is_cancelled (dev)) { @@ -1486,11 +1492,12 @@ elanspi_fp_capture_ssm_handler (FpiSsm *ssm, FpDevice *dev) if (self->deactivating) { fp_dbg (" got deactivate; exiting"); + + self->deactivating = FALSE; fpi_ssm_mark_completed (ssm); /* mark deactivate done */ fpi_image_device_deactivate_complete (FP_IMAGE_DEVICE (dev), NULL); - self->deactivating = FALSE; return; } From eb1013cdb6bcb22e52c22973eeac60b2c668d9ae Mon Sep 17 00:00:00 2001 From: Aris Lin Date: Thu, 27 Jan 2022 11:14:31 +0800 Subject: [PATCH 09/27] synaptics: Remove PID 0xC9 --- data/autosuspend.hwdb | 2 +- libfprint/drivers/synaptics/synaptics.c | 1 - libfprint/fprint-list-udev-hwdb.c | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index cbc6109f..8bbc52c8 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -181,7 +181,6 @@ usb:v06CBp00DF* usb:v06CBp00F9* usb:v06CBp00FC* usb:v06CBp00C2* -usb:v06CBp00C9* usb:v06CBp0100* usb:v06CBp00F0* usb:v06CBp0103* @@ -277,6 +276,7 @@ usb:v06CBp00D8* usb:v06CBp00DA* usb:v06CBp00E7* usb:v06CBp00E9* +usb:v06CBp00C9* usb:v0A5Cp5801* usb:v0A5Cp5805* usb:v0A5Cp5834* diff --git a/libfprint/drivers/synaptics/synaptics.c b/libfprint/drivers/synaptics/synaptics.c index 98627ed7..7a2c6ebe 100644 --- a/libfprint/drivers/synaptics/synaptics.c +++ b/libfprint/drivers/synaptics/synaptics.c @@ -36,7 +36,6 @@ static const FpIdEntry id_table[] = { { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00F9, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00FC, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00C2, }, - { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00C9, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0100, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x00F0, }, { .vid = SYNAPTICS_VENDOR_ID, .pid = 0x0103, }, diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index f5674574..19b49e83 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -51,6 +51,7 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x06cb, .pid = 0x00da }, { .vid = 0x06cb, .pid = 0x00e7 }, { .vid = 0x06cb, .pid = 0x00e9 }, + { .vid = 0x06cb, .pid = 0x00c9 }, { .vid = 0x0a5c, .pid = 0x5801 }, { .vid = 0x0a5c, .pid = 0x5805 }, { .vid = 0x0a5c, .pid = 0x5834 }, From 038c7108a66fb9d69b7bfbe2e22b987f5de41573 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Mon, 27 Dec 2021 13:44:16 +0100 Subject: [PATCH 10/27] goodixmoc: Further template parsing fixes In commit 5c28654d9 ("goodixmoc: Fix print template parsing") the length check for the verify and duplicate check responses by requiring two extra bytes at the end of the message. There were also issues in other places where the length was not checked correctly, including a scenario that could cause a read beyond the end of the buffer. Related: #444 --- libfprint/drivers/goodixmoc/goodix_proto.c | 23 ++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/libfprint/drivers/goodixmoc/goodix_proto.c b/libfprint/drivers/goodixmoc/goodix_proto.c index 01044a94..b615dbaa 100644 --- a/libfprint/drivers/goodixmoc/goodix_proto.c +++ b/libfprint/drivers/goodixmoc/goodix_proto.c @@ -259,12 +259,9 @@ gx_proto_parse_fingerid ( if (buffer[Offset++] != 67) return -1; - fid_buffer_size--; template->type = buffer[Offset++]; - fid_buffer_size--; template->finger_index = buffer[Offset++]; - fid_buffer_size--; Offset++; memcpy (template->accountid, &buffer[Offset], sizeof (template->accountid)); Offset += sizeof (template->accountid); @@ -273,6 +270,8 @@ gx_proto_parse_fingerid ( template->payload.size = buffer[Offset++]; if (template->payload.size > sizeof (template->payload.data)) return -1; + if (template->payload.size + Offset > fid_buffer_size) + return -1; memset (template->payload.data, 0, template->payload.size); memcpy (template->payload.data, &buffer[Offset], template->payload.size); @@ -365,9 +364,12 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c if (buffer_len < 3) return -1; uint16_t tid_size = GUINT16_FROM_LE (*(uint16_t *) (buffer + 1)); - if ((buffer_len < tid_size + 3) || (buffer_len > sizeof (template_format_t)) + 3) + offset += 3; + + if (buffer_len < tid_size + offset) + return -1; + if (gx_proto_parse_fingerid (buffer + offset, tid_size, &presp->check_duplicate_resp.template) != 0) return -1; - memcpy (&presp->check_duplicate_resp.template, buffer + 3, tid_size); } break; @@ -380,9 +382,12 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c fingerlist = buffer + 2; for(uint8_t num = 0; num < presp->finger_list_resp.finger_num; num++) { - uint16_t fingerid_length = GUINT16_FROM_LE (*(uint16_t *) (fingerlist + offset)); + uint16_t fingerid_length; + if (buffer_len < offset + 2) + return -1; + fingerid_length = GUINT16_FROM_LE (*(uint16_t *) (fingerlist + offset)); offset += 2; - if (buffer_len < fingerid_length + offset + 2) + if (buffer_len < fingerid_length + offset) return -1; if (gx_proto_parse_fingerid (fingerlist + offset, fingerid_length, @@ -405,7 +410,7 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c presp->verify.match = (buffer[0] == 0) ? true : false; if (presp->verify.match) { - if (buffer_len < sizeof (template_format_t) + 10) + if (buffer_len < 10) return -1; offset += 1; presp->verify.rejectdetail = GUINT16_FROM_LE (*(uint16_t *) (buffer + offset)); @@ -416,6 +421,8 @@ gx_proto_parse_body (uint16_t cmd, uint8_t *buffer, uint16_t buffer_len, pgxfp_c offset += 1; fingerid_size = GUINT16_FROM_LE (*(uint16_t *) (buffer + offset)); offset += 2; + if (buffer_len < fingerid_size + offset) + return -1; if (gx_proto_parse_fingerid (buffer + offset, fingerid_size, &presp->verify.template) != 0) { presp->result = GX_FAILED; From ae5696a9bb5cf2fbc589430853239586f7bcd54f Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 28 Jan 2022 17:29:53 +0100 Subject: [PATCH 11/27] goodixmoc: Change error message for corrupted headers Otherwise you can't tell from the log whether parsing the body or header failed. --- libfprint/drivers/goodixmoc/goodix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 15a85323..ad5a58d4 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -159,7 +159,7 @@ fp_cmd_receive_cb (FpiUsbTransfer *transfer, { fpi_ssm_mark_failed (transfer->ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Corrupted message received")); + "Corrupted message header received")); return; } From 168ab980219a3265b0f70e6f19edd65e6d9fc59c Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 28 Jan 2022 17:30:37 +0100 Subject: [PATCH 12/27] examples: Check whether the returned date is valid Prints may have an invalid date. Extend the checks so that this is also caught in addition to a NULL date. --- examples/identify.c | 4 +++- examples/manage-prints.c | 2 +- examples/verify.c | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/identify.c b/examples/identify.c index bc2fe00d..dc6a5d5c 100644 --- a/examples/identify.c +++ b/examples/identify.c @@ -143,6 +143,7 @@ on_identify_cb (FpDevice *dev, FpPrint *match, FpPrint *print, if (match) { g_autoptr(FpPrint) matched_print = g_object_ref (match); + const GDate *date; char date_str[128] = {}; identify_data->ret_value = EXIT_SUCCESS; @@ -155,7 +156,8 @@ on_identify_cb (FpDevice *dev, FpPrint *match, FpPrint *print, matched_print = g_steal_pointer (&stored_print); } - if (fp_print_get_enroll_date (matched_print)) + date = fp_print_get_enroll_date (matched_print); + if (date && g_date_valid (date)) g_date_strftime (date_str, G_N_ELEMENTS (date_str), "%Y-%m-%d\0", fp_print_get_enroll_date (matched_print)); else diff --git a/examples/manage-prints.c b/examples/manage-prints.c index 4d206cc9..88200a35 100644 --- a/examples/manage-prints.c +++ b/examples/manage-prints.c @@ -161,7 +161,7 @@ on_list_completed (FpDevice *dev, finger_to_string (fp_print_get_finger (print)), fp_print_get_username (print)); - if (date) + if (date && g_date_valid (date)) { g_date_strftime (buf, G_N_ELEMENTS (buf), "%Y-%m-%d\0", date); g_print (", enrolled on %s", buf); diff --git a/examples/verify.c b/examples/verify.c index 4b16323d..6892ef0e 100644 --- a/examples/verify.c +++ b/examples/verify.c @@ -130,12 +130,14 @@ on_match_cb (FpDevice *dev, FpPrint *match, FpPrint *print, if (match) { - char date_str[128]; + const GDate *date = fp_print_get_enroll_date (match); + char date_str[128] = ""; verify_data->ret_value = EXIT_SUCCESS; - g_date_strftime (date_str, G_N_ELEMENTS (date_str), "%Y-%m-%d\0", - fp_print_get_enroll_date (match)); + if (date && g_date_valid (date)) + g_date_strftime (date_str, G_N_ELEMENTS (date_str), "%Y-%m-%d\0", + fp_print_get_enroll_date (match)); g_debug ("Match report: device %s matched finger %s successifully " "with print %s, enrolled on date %s by user %s", fp_device_get_name (dev), From e0fd178bec9b887386fd6e6d7829a9756f82e3d8 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 28 Jan 2022 18:14:05 +0100 Subject: [PATCH 13/27] goodixmoc: Log which the ID that produced the duplicate --- libfprint/drivers/goodixmoc/goodix.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index ad5a58d4..8ca6a366 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -764,9 +764,14 @@ fp_enroll_check_duplicate_cb (FpiDeviceGoodixMoc *self, } if (resp->check_duplicate_resp.duplicate) { + g_autoptr(FpPrint) print = NULL; + + print = g_object_ref_sink (fp_print_from_template (self, &resp->check_duplicate_resp.template)); + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_DUPLICATE, - "Finger has already enrolled")); + "Finger was already enrolled as '%s'", + fp_print_get_description (print))); return; } From 9ce6ed4164399de36695349f95602e3bece33b7e Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 3 Feb 2022 14:46:40 +0100 Subject: [PATCH 14/27] goodixmoc: Report recognized print after a match failure The API should return the recognized print, even if none of the prints given in the gallery (or the one passed to verify) matched. Without this the garbage-collection of left-over prints does not work, causing issues after reinstall. Fixes: #444 --- libfprint/drivers/goodixmoc/goodix.c | 49 ++++++++++------------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 8ca6a366..43f63259 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -420,12 +420,9 @@ fp_verify_cb (FpiDeviceGoodixMoc *self, gxfp_cmd_response_t *resp, GError *error) { - g_autoptr(GPtrArray) templates = NULL; FpDevice *device = FP_DEVICE (self); - FpPrint *match = NULL; - FpPrint *print = NULL; - gint cnt = 0; - gboolean find = false; + FpPrint *new_scan = NULL; + FpPrint *matching = NULL; if (error) { @@ -434,46 +431,34 @@ fp_verify_cb (FpiDeviceGoodixMoc *self, } if (resp->verify.match) { - match = fp_print_from_template (self, &resp->verify.template); + new_scan = fp_print_from_template (self, &resp->verify.template); if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) { - templates = g_ptr_array_sized_new (1); - fpi_device_get_verify_data (device, &print); - g_ptr_array_add (templates, print); + fpi_device_get_verify_data (device, &matching); + if (!fp_print_equal (matching, new_scan)) + matching = NULL; } else { + GPtrArray *templates = NULL; fpi_device_get_identify_data (device, &templates); - g_ptr_array_ref (templates); - } - for (cnt = 0; cnt < templates->len; cnt++) - { - print = g_ptr_array_index (templates, cnt); - if (fp_print_equal (print, match)) + for (gint i = 0; i < templates->len; i++) { - find = true; - break; + if (fp_print_equal (g_ptr_array_index (templates, i), new_scan)) + { + matching = g_ptr_array_index (templates, i); + break; + } } - - } - if (find) - { - if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) - fpi_device_verify_report (device, FPI_MATCH_SUCCESS, match, error); - else - fpi_device_identify_report (device, print, match, error); } } - if (!find) - { - if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) - fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error); - else - fpi_device_identify_report (device, NULL, NULL, error); - } + if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, matching ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL, new_scan, error); + else + fpi_device_identify_report (device, matching, new_scan, error); fpi_ssm_next_state (self->task_ssm); From 356805168637666c758fbc2a2f436d6418c7e5f4 Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Fri, 5 Nov 2021 07:27:56 -0700 Subject: [PATCH 15/27] goodixmoc: support for clear_storage The internal storage of this device can get messed up by other operating systems, so it's handy to be able to clear it. I'm not 100% sure whether the commands I've sent to the device are exactly what is supposed to be used (just a guess), but it did seem to work, and it even fixed another issue I had. --- libfprint/drivers/goodixmoc/goodix.c | 40 ++++++++++++++++++++++++++++ tests/goodixmoc/custom.py | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/libfprint/drivers/goodixmoc/goodix.c b/libfprint/drivers/goodixmoc/goodix.c index 43f63259..4eeb7215 100644 --- a/libfprint/drivers/goodixmoc/goodix.c +++ b/libfprint/drivers/goodixmoc/goodix.c @@ -1160,6 +1160,32 @@ fp_template_delete_cb (FpiDeviceGoodixMoc *self, fp_info ("Successfully deleted enrolled user"); fpi_device_delete_complete (device, NULL); } + +static void +fp_template_delete_all_cb (FpiDeviceGoodixMoc *self, + gxfp_cmd_response_t *resp, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + if (error) + { + fpi_device_clear_storage_complete (device, error); + return; + } + if ((resp->result >= GX_FAILED) && (resp->result != GX_ERROR_FINGER_ID_NOEXIST)) + { + fpi_device_clear_storage_complete (FP_DEVICE (self), + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Failed clear storage, result: 0x%x", + resp->result)); + return; + } + + fp_info ("Successfully cleared storage"); + fpi_device_clear_storage_complete (device, NULL); +} + /****************************************************************************** * * fp_template_list Function @@ -1483,6 +1509,19 @@ gx_fp_template_delete (FpDevice *device) } +static void +gx_fp_template_delete_all (FpDevice *device) +{ + FpiDeviceGoodixMoc *self = FPI_DEVICE_GOODIXMOC (device); + + goodix_sensor_cmd (self, MOC_CMD0_DELETETEMPLATE, MOC_CMD1_DELETE_ALL, + false, + NULL, + 0, + fp_template_delete_all_cb); + +} + static void fpi_device_goodixmoc_init (FpiDeviceGoodixMoc *self) { @@ -1526,6 +1565,7 @@ fpi_device_goodixmoc_class_init (FpiDeviceGoodixMocClass *klass) dev_class->probe = gx_fp_probe; dev_class->enroll = gx_fp_enroll; dev_class->delete = gx_fp_template_delete; + dev_class->clear_storage = gx_fp_template_delete_all; dev_class->list = gx_fp_template_list; dev_class->verify = gx_fp_verify_identify; dev_class->identify = gx_fp_verify_identify; diff --git a/tests/goodixmoc/custom.py b/tests/goodixmoc/custom.py index 2fe6edd9..1fb513a1 100755 --- a/tests/goodixmoc/custom.py +++ b/tests/goodixmoc/custom.py @@ -21,7 +21,7 @@ assert d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) assert d.has_feature(FPrint.DeviceFeature.STORAGE) assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) -assert not d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) d.open_sync() From d3014f1684eb9f8124dc0e1d22b7f6f7c8a8462d Mon Sep 17 00:00:00 2001 From: Doomsdayrs Date: Fri, 24 Dec 2021 18:31:03 +0000 Subject: [PATCH 16/27] Delete TODO --- TODO | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index 5d1034d2..00000000 --- a/TODO +++ /dev/null @@ -1,30 +0,0 @@ -LIBRARY -======= -test suite against NFIQ compliance set -make library optionally asynchronous and maybe thread-safe -nbis cleanups -API function to determine if img device supports uncond. capture -race-free way of saying "save this print but don't overwrite" - -NEW DRIVERS -=========== -Sunplus 895 driver -AES3400/3500 driver -ID Mouse driver -Support for 2nd generation MS devices -Support for 2nd generation UPEK devices - -IMAGING -======= -ignore first frame or two with aes2501 -aes2501: increase threshold "sum" for end-of-image detection -aes2501 gain calibration -aes4000 gain calibration -aes4000 resampling -PPMM parameter to get_minutiae seems to have no effect -nbis minutiae should be stored in endian-independent format - -PORTABILITY -=========== -OpenBSD can't do -Wshadow or visibility -OpenBSD: add compat codes for ENOTSUP ENODATA and EPROTO From 754ccfb8651fcd8453a39c1236473e88fe4e254b Mon Sep 17 00:00:00 2001 From: doomsdayrs Date: Thu, 23 Dec 2021 23:12:54 -0500 Subject: [PATCH 17/27] Convert README to markdown Just a minor change, but makes the file a bit more readable. --- README => README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename README => README.md (98%) diff --git a/README b/README.md similarity index 98% rename from README rename to README.md index fbf76705..7f59b4e8 100644 --- a/README +++ b/README.md @@ -1,9 +1,10 @@ -libfprint -========= +# libfprint libfprint is part of the fprint project: https://fprint.freedesktop.org/ +## History + libfprint was originally developed as part of an academic project at the University of Manchester with the aim of hiding differences between different consumer fingerprint scanners and providing a single uniform API to application @@ -15,6 +16,8 @@ from this one, although I try to keep them as similar as possible (I'm not hiding anything in the academic branch, it's just the open source release contains some commits excluded from the academic project). +## License + THE UNIVERSITY OF MANCHESTER DOES NOT ENDORSE THIS THIS SOFTWARE RELEASE AND IS IN NO WAY RESPONSIBLE FOR THE CODE CONTAINED WITHIN, OR ANY DAMAGES CAUSED BY USING OR DISTRIBUTING THE SOFTWARE. Development does not happen on From 6f5ba3cbb5925691b09dbc5cd9852cb95f2def37 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 11 Feb 2022 19:34:59 +0100 Subject: [PATCH 18/27] udev-hwdb: Update unsupported device list --- data/autosuspend.hwdb | 9 ++++++++- libfprint/fprint-list-udev-hwdb.c | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index 8bbc52c8..91a14ebd 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -267,16 +267,19 @@ usb:v06CBp008A* usb:v06CBp009A* usb:v06CBp009B* usb:v06CBp00A2* +usb:v06CBp00A8* usb:v06CBp00B7* usb:v06CBp00BB* usb:v06CBp00BE* usb:v06CBp00C4* usb:v06CBp00CB* +usb:v06CBp00C9* usb:v06CBp00D8* usb:v06CBp00DA* +usb:v06CBp00DC* usb:v06CBp00E7* usb:v06CBp00E9* -usb:v06CBp00C9* +usb:v06CBp00FD* usb:v0A5Cp5801* usb:v0A5Cp5805* usb:v0A5Cp5834* @@ -288,6 +291,7 @@ usb:v0A5Cp5844* usb:v0A5Cp5845* usb:v0BDAp5812* usb:v10A5p0007* +usb:v10A5p9200* usb:v1188p9545* usb:v138Ap0007* usb:v138Ap003A* @@ -305,6 +309,7 @@ usb:v1491p0088* usb:v16D1p1027* usb:v1C7Ap0300* usb:v1C7Ap0575* +usb:v1C7Ap0576* usb:v27C6p5042* usb:v27C6p5110* usb:v27C6p5117* @@ -324,7 +329,9 @@ usb:v27C6p55A2* usb:v27C6p55A4* usb:v27C6p55B4* usb:v27C6p5740* +usb:v27C6p5E0A* usb:v2808p9338* +usb:v298Dp2020* usb:v298Dp2033* usb:v3538p0930* ID_AUTOSUSPEND=1 diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index 19b49e83..ad9cdd07 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -42,16 +42,19 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x06cb, .pid = 0x009a }, { .vid = 0x06cb, .pid = 0x009b }, { .vid = 0x06cb, .pid = 0x00a2 }, + { .vid = 0x06cb, .pid = 0x00a8 }, { .vid = 0x06cb, .pid = 0x00b7 }, { .vid = 0x06cb, .pid = 0x00bb }, { .vid = 0x06cb, .pid = 0x00be }, { .vid = 0x06cb, .pid = 0x00c4 }, { .vid = 0x06cb, .pid = 0x00cb }, + { .vid = 0x06cb, .pid = 0x00c9 }, { .vid = 0x06cb, .pid = 0x00d8 }, { .vid = 0x06cb, .pid = 0x00da }, + { .vid = 0x06cb, .pid = 0x00dc }, { .vid = 0x06cb, .pid = 0x00e7 }, { .vid = 0x06cb, .pid = 0x00e9 }, - { .vid = 0x06cb, .pid = 0x00c9 }, + { .vid = 0x06cb, .pid = 0x00fd }, { .vid = 0x0a5c, .pid = 0x5801 }, { .vid = 0x0a5c, .pid = 0x5805 }, { .vid = 0x0a5c, .pid = 0x5834 }, @@ -63,6 +66,7 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x0a5c, .pid = 0x5845 }, { .vid = 0x0bda, .pid = 0x5812 }, { .vid = 0x10a5, .pid = 0x0007 }, + { .vid = 0x10a5, .pid = 0x9200 }, { .vid = 0x1188, .pid = 0x9545 }, { .vid = 0x138a, .pid = 0x0007 }, { .vid = 0x138a, .pid = 0x003a }, @@ -80,6 +84,7 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x16d1, .pid = 0x1027 }, { .vid = 0x1c7a, .pid = 0x0300 }, { .vid = 0x1c7a, .pid = 0x0575 }, + { .vid = 0x1c7a, .pid = 0x0576 }, { .vid = 0x27c6, .pid = 0x5042 }, { .vid = 0x27c6, .pid = 0x5110 }, { .vid = 0x27c6, .pid = 0x5117 }, @@ -99,7 +104,9 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x27c6, .pid = 0x55a4 }, { .vid = 0x27c6, .pid = 0x55b4 }, { .vid = 0x27c6, .pid = 0x5740 }, + { .vid = 0x27c6, .pid = 0x5e0a }, { .vid = 0x2808, .pid = 0x9338 }, + { .vid = 0x298d, .pid = 0x2020 }, { .vid = 0x298d, .pid = 0x2033 }, { .vid = 0x3538, .pid = 0x0930 }, { .vid = 0 }, From 8fad2652ee75836e5a60c73af0f06cc8b57a07f5 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 11 Feb 2022 18:35:15 +0100 Subject: [PATCH 19/27] Release 1.94.3 --- NEWS | 10 ++++++++++ meson.build | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index eacd99c9..2e0a2c6a 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,16 @@ This file lists notable changes in each release. For the full history of all changes, see ChangeLog. +2021-11-02: v1.94.3 release + +Highlights: + * Ensure idle mainloop before completing enumeration (fprintd#119) + * It is now possible to extend already enrolled prints + * elanspi: Fix timeout error with some hardware (#438) + * elanspi: Fix cancellation issues + * goodixmoc: Return matching device print; fixes duplicate checking (#444) + * goodixmoc: Support clearing the storage (usually unused) + 2021-11-02: v1.94.2 release Highlights: diff --git a/meson.build b/meson.build index f2750519..c937d281 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('libfprint', [ 'c', 'cpp' ], - version: '1.94.2', + version: '1.94.3', license: 'LGPLv2.1+', default_options: [ 'buildtype=debugoptimized', From 600e6f2107ad23854edd2e4d0ad4f91f7eb0870e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 18:49:16 +0100 Subject: [PATCH 20/27] tests/meson: Remove unsupported sources_dir from gnome mkenums --- tests/meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/meson.build b/tests/meson.build index e1eaedc8..61ee43c9 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -279,7 +279,6 @@ if get_option('tod') ) fp_todv1_enums = gnome.mkenums_simple('fp-todv1-enums', - source_dir: 'tod-drivers', sources: [ 'tod-drivers/base-fp-device.h', 'tod-drivers/base-fp-print.h', From b6ef314434e48fad7a460b727cc376d59c042f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 18:50:11 +0100 Subject: [PATCH 21/27] tests/tod-drivers: Move FpDeviceFeature definitions to base-fp-device They're public enums, so put there in the expected file. --- tests/tod-drivers/base-fp-device.h | 25 +++++++++++++++++++++++++ tests/tod-drivers/base-fpi-device.h | 25 ------------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/tod-drivers/base-fp-device.h b/tests/tod-drivers/base-fp-device.h index 5d3bbc26..45cdb98f 100644 --- a/tests/tod-drivers/base-fp-device.h +++ b/tests/tod-drivers/base-fp-device.h @@ -86,6 +86,31 @@ typedef enum { FP_DEVICE_ERROR_TODV1_90_4_REMOVED = 0x100, } FpDeviceErrorTODV1_90_4; +typedef enum /*< flags >*/ { + FP_DEVICE_FEATURE_TODV1_92_0_NONE = 0, + FP_DEVICE_FEATURE_TODV1_92_0_CAPTURE = 1 << 0, + FP_DEVICE_FEATURE_TODV1_92_0_IDENTIFY = 1 << 1, + FP_DEVICE_FEATURE_TODV1_92_0_VERIFY = 1 << 2, + FP_DEVICE_FEATURE_TODV1_92_0_STORAGE = 1 << 3, + FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_LIST = 1 << 4, + FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_DELETE = 1 << 5, + FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_CLEAR = 1 << 6, + FP_DEVICE_FEATURE_TODV1_92_0_DUPLICATES_CHECK = 1 << 7, +} FpDeviceFeatureTODV1_92_0; + +typedef enum /*< flags >*/ { + FP_DEVICE_FEATURE_TODV1_94_0_NONE = 0, + FP_DEVICE_FEATURE_TODV1_94_0_CAPTURE = 1 << 0, + FP_DEVICE_FEATURE_TODV1_94_0_IDENTIFY = 1 << 1, + FP_DEVICE_FEATURE_TODV1_94_0_VERIFY = 1 << 2, + FP_DEVICE_FEATURE_TODV1_94_0_STORAGE = 1 << 3, + FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_LIST = 1 << 4, + FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_DELETE = 1 << 5, + FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_CLEAR = 1 << 6, + FP_DEVICE_FEATURE_TODV1_94_0_DUPLICATES_CHECK = 1 << 7, + FP_DEVICE_FEATURE_TODV1_94_0_ALWAYS_ON = 1 << 8, +} FpDeviceFeatureTODV1_94_0; + typedef enum { FP_DEVICE_ERROR_TODV1_94_0_GENERAL, FP_DEVICE_ERROR_TODV1_94_0_NOT_SUPPORTED, diff --git a/tests/tod-drivers/base-fpi-device.h b/tests/tod-drivers/base-fpi-device.h index 0e847611..222ff088 100644 --- a/tests/tod-drivers/base-fpi-device.h +++ b/tests/tod-drivers/base-fpi-device.h @@ -110,31 +110,6 @@ typedef enum { FPI_DEVICE_ACTION_TODV1_92_0_CLEAR_STORAGE } FpiDeviceActionTODV1_92_0; -typedef enum /*< flags >*/ { - FP_DEVICE_FEATURE_TODV1_92_0_NONE = 0, - FP_DEVICE_FEATURE_TODV1_92_0_CAPTURE = 1 << 0, - FP_DEVICE_FEATURE_TODV1_92_0_IDENTIFY = 1 << 1, - FP_DEVICE_FEATURE_TODV1_92_0_VERIFY = 1 << 2, - FP_DEVICE_FEATURE_TODV1_92_0_STORAGE = 1 << 3, - FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_LIST = 1 << 4, - FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_DELETE = 1 << 5, - FP_DEVICE_FEATURE_TODV1_92_0_STORAGE_CLEAR = 1 << 6, - FP_DEVICE_FEATURE_TODV1_92_0_DUPLICATES_CHECK = 1 << 7, -} FpDeviceFeatureTODV1_92_0; - -typedef enum /*< flags >*/ { - FP_DEVICE_FEATURE_TODV1_94_0_NONE = 0, - FP_DEVICE_FEATURE_TODV1_94_0_CAPTURE = 1 << 0, - FP_DEVICE_FEATURE_TODV1_94_0_IDENTIFY = 1 << 1, - FP_DEVICE_FEATURE_TODV1_94_0_VERIFY = 1 << 2, - FP_DEVICE_FEATURE_TODV1_94_0_STORAGE = 1 << 3, - FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_LIST = 1 << 4, - FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_DELETE = 1 << 5, - FP_DEVICE_FEATURE_TODV1_94_0_STORAGE_CLEAR = 1 << 6, - FP_DEVICE_FEATURE_TODV1_94_0_DUPLICATES_CHECK = 1 << 7, - FP_DEVICE_FEATURE_TODV1_94_0_ALWAYS_ON = 1 << 8, -} FpDeviceFeatureTODV1_94_0; - typedef enum { FPI_DEVICE_UDEV_SUBTYPE_TODV1_92_0_SPIDEV = 1 << 0, FPI_DEVICE_UDEV_SUBTYPE_TODV1_92_0_HIDRAW = 1 << 1, From a328dfe1d9fb975c012f4c5823d0674b27357dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 18:50:47 +0100 Subject: [PATCH 22/27] tests/tod-drivers: Add tests for FpDeviceFeature on 1.94.3 --- tests/test-fp-todv1-types.c | 1 + tests/tod-drivers/base-fp-device.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/tests/test-fp-todv1-types.c b/tests/test-fp-todv1-types.c index 830cee75..34582b97 100644 --- a/tests/test-fp-todv1-types.c +++ b/tests/test-fp-todv1-types.c @@ -283,6 +283,7 @@ test_device_public_enums (void) check_type_compatibility (FP_TYPE_DEVICE_ERROR, 1, 94, 0); check_type_compatibility (FP_TYPE_DEVICE_FEATURE, 1, 92, 0); check_type_compatibility (FP_TYPE_DEVICE_FEATURE, 1, 94, 0); + check_type_compatibility (FP_TYPE_DEVICE_FEATURE, 1, 94, 3); check_type_compatibility (FP_TYPE_TEMPERATURE, 1, 94, 0); check_type_compatibility (FPI_TYPE_DEVICE_UDEV_SUBTYPE_FLAGS, 1, 92, 0); } diff --git a/tests/tod-drivers/base-fp-device.h b/tests/tod-drivers/base-fp-device.h index 45cdb98f..ee3d09c4 100644 --- a/tests/tod-drivers/base-fp-device.h +++ b/tests/tod-drivers/base-fp-device.h @@ -111,6 +111,20 @@ typedef enum /*< flags >*/ { FP_DEVICE_FEATURE_TODV1_94_0_ALWAYS_ON = 1 << 8, } FpDeviceFeatureTODV1_94_0; +typedef enum /*< flags >*/ { + FP_DEVICE_FEATURE_TODV1_94_3_NONE = 0, + FP_DEVICE_FEATURE_TODV1_94_3_CAPTURE = 1 << 0, + FP_DEVICE_FEATURE_TODV1_94_3_IDENTIFY = 1 << 1, + FP_DEVICE_FEATURE_TODV1_94_3_VERIFY = 1 << 2, + FP_DEVICE_FEATURE_TODV1_94_3_STORAGE = 1 << 3, + FP_DEVICE_FEATURE_TODV1_94_3_STORAGE_LIST = 1 << 4, + FP_DEVICE_FEATURE_TODV1_94_3_STORAGE_DELETE = 1 << 5, + FP_DEVICE_FEATURE_TODV1_94_3_STORAGE_CLEAR = 1 << 6, + FP_DEVICE_FEATURE_TODV1_94_3_DUPLICATES_CHECK = 1 << 7, + FP_DEVICE_FEATURE_TODV1_94_3_ALWAYS_ON = 1 << 8, + FP_DEVICE_FEATURE_TODV1_94_3_UPDATE_PRINT = 1 << 9, +} FpDeviceFeatureTODV1_94_3; + typedef enum { FP_DEVICE_ERROR_TODV1_94_0_GENERAL, FP_DEVICE_ERROR_TODV1_94_0_NOT_SUPPORTED, From 20de7400c6061474ada2ba2b0a56b663bda3a2e3 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Mon, 14 Feb 2022 17:47:33 +0100 Subject: [PATCH 23/27] tests: Avoid -Wdangling-pointer warning The code is correct, but gcc thinks the pointer is still NULL after the call. As obvious workaround don't seem to work, just disable the warning for now. --- tests/test-fpi-device.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test-fpi-device.c b/tests/test-fpi-device.c index 3d0621ac..a7b2611b 100644 --- a/tests/test-fpi-device.c +++ b/tests/test-fpi-device.c @@ -147,6 +147,10 @@ tod_check_device_version (FpDevice *device_class, typedef FpDevice FpAutoCloseDevice; +/* gcc 12.0.1 is complaining about dangling pointers in the auto_close* functions */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-pointer" + static FpAutoCloseDevice * auto_close_fake_device_new (void) { @@ -179,6 +183,8 @@ auto_close_fake_device_free (FpAutoCloseDevice *device) } G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpAutoCloseDevice, auto_close_fake_device_free) +#pragma GCC diagnostic pop + typedef FpDeviceClass FpAutoResetClass; static FpAutoResetClass default_fake_dev_class = {0}; From f3187ad9f45e075f4c8d429d2b698299e2ebc79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 19:35:12 +0100 Subject: [PATCH 24/27] tod: Ensure we install tod-macros.h header --- libfprint/tod/meson.build | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libfprint/tod/meson.build b/libfprint/tod/meson.build index ba395c16..10c49e4c 100644 --- a/libfprint/tod/meson.build +++ b/libfprint/tod/meson.build @@ -75,6 +75,10 @@ pkgconfig.generate(libfprint_tod, ] ) +tod_local_headers = [ + 'tod-macros.h', +] + tod_headers = [] extra_libfprint_headers = [ 'drivers_api.h', @@ -95,3 +99,7 @@ custom_target('tod_fpi_enums_headers', install_headers(tod_headers, subdir: tod_subpath ) + +install_headers(tod_local_headers, + subdir: tod_subpath / 'tod', +) From a1340485b655591d15b76294c582b071e159a90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 19:43:58 +0100 Subject: [PATCH 25/27] tests/tod-drivers: Add tests for 1.94.3-built tod drivers --- tests/meson.build | 1 + ...evice-fake-tod-ssm-test-v1+1.94.3-x86_64.so | Bin 0 -> 131584 bytes ...ce-fake-tod-test-driver-v1+1.94.3-x86_64.so | Bin 0 -> 44968 bytes 3 files changed, 1 insertion(+) create mode 100755 tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so create mode 100755 tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so diff --git a/tests/meson.build b/tests/meson.build index 61ee43c9..48fece68 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -325,6 +325,7 @@ if get_option('tod') 'v1+1.90.3', 'v1+1.90.5', 'v1+1.94.0', + 'v1+1.94.3', ] foreach tod_version: tod_test_versions diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-ssm-test-v1+1.94.3-x86_64.so new file mode 100755 index 0000000000000000000000000000000000000000..b8934d1a75fae9713f7e9e6a78a3cf5469e71d43 GIT binary patch literal 131584 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 X(wP2C;z^6d`0`IRevK&F2mb#7`ZGyN literal 0 HcmV?d00001 diff --git a/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so b/tests/tod-drivers/tod-x86_64-v1+1.94.3/libdevice-fake-tod-test-driver-v1+1.94.3-x86_64.so new file mode 100755 index 0000000000000000000000000000000000000000..e373a3845566e71c34ac609c17f587f43b23d888 GIT binary patch literal 44968 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 literal 0 HcmV?d00001 From 31f8d8c0671ed8410bcdfe406134ef7e43c05fbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 14 Feb 2022 20:06:20 +0100 Subject: [PATCH 26/27] libfprint-tod.ver: Add new APIs to 1.94 symbols --- libfprint/tod/libfprint-tod.ver.in | 1 + 1 file changed, 1 insertion(+) diff --git a/libfprint/tod/libfprint-tod.ver.in b/libfprint/tod/libfprint-tod.ver.in index 8166f164..40680f8c 100644 --- a/libfprint/tod/libfprint-tod.ver.in +++ b/libfprint/tod/libfprint-tod.ver.in @@ -30,4 +30,5 @@ global: fpi_device_critical_leave; fpi_device_resume_complete; fpi_device_suspend_complete; + fpi_ssm_silence_debug; } LIBFPRINT_TOD_@tod_soversion@_1.92; From 8c5a4e3b415ff921d035d855822f80565e0e7861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 15 Feb 2022 13:04:47 +0100 Subject: [PATCH 27/27] libfprint-tod.ver: Use explicit versioning for old symbols Given that we won't go in the past, it's better to be explicit in using versions for symbols so that all new symbols will be added to the latest listed release, while others won't be moved from where they are. --- libfprint/tod/libfprint-tod.ver.in | 223 ++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 4 deletions(-) diff --git a/libfprint/tod/libfprint-tod.ver.in b/libfprint/tod/libfprint-tod.ver.in index 40680f8c..496d5252 100644 --- a/libfprint/tod/libfprint-tod.ver.in +++ b/libfprint/tod/libfprint-tod.ver.in @@ -1,10 +1,212 @@ LIBFPRINT_TOD_@tod_soversion@.0.0 { global: - fpi_*; - fpi_ssm_new_full; + fpi_assemble_frames; + fpi_assemble_lines; + fpi_byte_reader_dup_data; + fpi_byte_reader_dup_string_utf16; + fpi_byte_reader_dup_string_utf32; + fpi_byte_reader_dup_string_utf8; + fpi_byte_reader_free; + fpi_byte_reader_get_data; + fpi_byte_reader_get_float32_be; + fpi_byte_reader_get_float32_le; + fpi_byte_reader_get_float64_be; + fpi_byte_reader_get_float64_le; + fpi_byte_reader_get_int16_be; + fpi_byte_reader_get_int16_le; + fpi_byte_reader_get_int24_be; + fpi_byte_reader_get_int24_le; + fpi_byte_reader_get_int32_be; + fpi_byte_reader_get_int32_le; + fpi_byte_reader_get_int64_be; + fpi_byte_reader_get_int64_le; + fpi_byte_reader_get_int8; + fpi_byte_reader_get_pos; + fpi_byte_reader_get_remaining; + fpi_byte_reader_get_size; + fpi_byte_reader_get_string_utf8; + fpi_byte_reader_get_sub_reader; + fpi_byte_reader_get_uint16_be; + fpi_byte_reader_get_uint16_le; + fpi_byte_reader_get_uint24_be; + fpi_byte_reader_get_uint24_le; + fpi_byte_reader_get_uint32_be; + fpi_byte_reader_get_uint32_le; + fpi_byte_reader_get_uint64_be; + fpi_byte_reader_get_uint64_le; + fpi_byte_reader_get_uint8; + fpi_byte_reader_init; + fpi_byte_reader_masked_scan_uint32; + fpi_byte_reader_masked_scan_uint32_peek; + fpi_byte_reader_new; + fpi_byte_reader_peek_data; + fpi_byte_reader_peek_float32_be; + fpi_byte_reader_peek_float32_le; + fpi_byte_reader_peek_float64_be; + fpi_byte_reader_peek_float64_le; + fpi_byte_reader_peek_int16_be; + fpi_byte_reader_peek_int16_le; + fpi_byte_reader_peek_int24_be; + fpi_byte_reader_peek_int24_le; + fpi_byte_reader_peek_int32_be; + fpi_byte_reader_peek_int32_le; + fpi_byte_reader_peek_int64_be; + fpi_byte_reader_peek_int64_le; + fpi_byte_reader_peek_int8; + fpi_byte_reader_peek_string_utf8; + fpi_byte_reader_peek_sub_reader; + fpi_byte_reader_peek_uint16_be; + fpi_byte_reader_peek_uint16_le; + fpi_byte_reader_peek_uint24_be; + fpi_byte_reader_peek_uint24_le; + fpi_byte_reader_peek_uint32_be; + fpi_byte_reader_peek_uint32_le; + fpi_byte_reader_peek_uint64_be; + fpi_byte_reader_peek_uint64_le; + fpi_byte_reader_peek_uint8; + fpi_byte_reader_set_pos; + fpi_byte_reader_skip; + fpi_byte_reader_skip_string_utf16; + fpi_byte_reader_skip_string_utf32; + fpi_byte_reader_skip_string_utf8; + fpi_byte_writer_ensure_free_space; + fpi_byte_writer_fill; + fpi_byte_writer_free; + fpi_byte_writer_free_and_get_data; + fpi_byte_writer_get_remaining; + fpi_byte_writer_init; + fpi_byte_writer_init_with_data; + fpi_byte_writer_init_with_size; + fpi_byte_writer_new; + fpi_byte_writer_new_with_data; + fpi_byte_writer_new_with_size; + fpi_byte_writer_put_data; + fpi_byte_writer_put_float32_be; + fpi_byte_writer_put_float32_le; + fpi_byte_writer_put_float64_be; + fpi_byte_writer_put_float64_le; + fpi_byte_writer_put_int16_be; + fpi_byte_writer_put_int16_le; + fpi_byte_writer_put_int24_be; + fpi_byte_writer_put_int24_le; + fpi_byte_writer_put_int32_be; + fpi_byte_writer_put_int32_le; + fpi_byte_writer_put_int64_be; + fpi_byte_writer_put_int64_le; + fpi_byte_writer_put_int8; + fpi_byte_writer_put_string_utf16; + fpi_byte_writer_put_string_utf32; + fpi_byte_writer_put_string_utf8; + fpi_byte_writer_put_uint16_be; + fpi_byte_writer_put_uint16_le; + fpi_byte_writer_put_uint24_be; + fpi_byte_writer_put_uint24_le; + fpi_byte_writer_put_uint32_be; + fpi_byte_writer_put_uint32_le; + fpi_byte_writer_put_uint64_be; + fpi_byte_writer_put_uint64_le; + fpi_byte_writer_put_uint8; + fpi_byte_writer_reset; + fpi_byte_writer_reset_and_get_data; + fpi_device_action_error; + fpi_device_action_get_type; + fpi_device_action_is_cancelled; + fpi_device_add_timeout; + fpi_device_capture_complete; + fpi_device_close_complete; + fpi_device_configure_wakeup; + fpi_device_delete_complete; + fpi_device_enroll_complete; + fpi_device_enroll_progress; + fpi_device_error_new; + fpi_device_error_new_msg; + fpi_device_get_cancellable; + fpi_device_get_capture_data; + fpi_device_get_current_action; + fpi_device_get_delete_data; + fpi_device_get_driver_data; + fpi_device_get_enroll_data; + fpi_device_get_identify_data; + fpi_device_get_usb_device; + fpi_device_get_verify_data; + fpi_device_get_virtual_env; + fpi_device_identify_complete; + fpi_device_identify_report; + fpi_device_list_complete; + fpi_device_open_complete; + fpi_device_probe_complete; + fpi_device_remove; + fpi_device_report_finger_status; + fpi_device_report_finger_status_changes; + fpi_device_retry_new; + fpi_device_retry_new_msg; + fpi_device_set_nr_enroll_stages; + fpi_device_set_scan_type; + fpi_device_udev_subtype_flags_get_type; + fpi_device_update_temp; + fpi_device_verify_complete; + fpi_device_verify_report; + fpi_do_movement_estimation; + fpi_image_device_activate; + fpi_image_device_activate_complete; + fpi_image_device_close_complete; + fpi_image_device_deactivate; + fpi_image_device_deactivate_complete; + fpi_image_device_image_captured; + fpi_image_device_open_complete; + fpi_image_device_report_finger_status; + fpi_image_device_retry_scan; + fpi_image_device_session_error; + fpi_image_device_set_bz3_threshold; + fpi_image_device_state_get_type; + fpi_image_flags_get_type; + fpi_image_resize; + fpi_match_result_get_type; + fpi_mean_sq_diff_norm; + fpi_print_add_from_image; + fpi_print_add_print; + fpi_print_bz3_match; + fpi_print_fill_from_user_id; + fpi_print_generate_user_id; + fpi_print_set_device_stored; + fpi_print_set_type; + fpi_print_type_get_type; + fpi_ssm_cancel_delayed_state_change; + fpi_ssm_dup_error; + fpi_ssm_free; + fpi_ssm_get_cur_state; + fpi_ssm_get_data; + fpi_ssm_get_error; + fpi_ssm_jump_to_state; fpi_ssm_jump_to_state_delayed; + fpi_ssm_jump_to_state_delayed_1_90; + fpi_ssm_mark_completed; fpi_ssm_mark_completed_delayed; + fpi_ssm_mark_completed_delayed_1_90; + fpi_ssm_mark_failed; + fpi_ssm_new_full; + fpi_ssm_new_full_1_90; + fpi_ssm_next_state; fpi_ssm_next_state_delayed; + fpi_ssm_next_state_delayed_1_90; + fpi_ssm_set_data; + fpi_ssm_start; + fpi_ssm_start_subsm; + fpi_ssm_usb_transfer_cb; + fpi_ssm_usb_transfer_with_weak_pointer_cb; + fpi_std_sq_dev; + fpi_transfer_type_get_type; + fpi_usb_transfer_fill_bulk; + fpi_usb_transfer_fill_bulk_full; + fpi_usb_transfer_fill_control; + fpi_usb_transfer_fill_interrupt; + fpi_usb_transfer_fill_interrupt_full; + fpi_usb_transfer_get_type; + fpi_usb_transfer_new; + fpi_usb_transfer_ref; + fpi_usb_transfer_submit; + fpi_usb_transfer_submit_sync; + fpi_usb_transfer_unref; local: *; }; @@ -15,13 +217,23 @@ global: fpi_device_clear_storage_complete; fpi_device_get_udev_data; fpi_device_update_features; - fpi_spi_*; + fpi_spi_transfer_get_type; + fpi_spi_transfer_new; + fpi_spi_transfer_read; + fpi_spi_transfer_read_full; + fpi_spi_transfer_ref; + fpi_spi_transfer_submit; + fpi_spi_transfer_submit_sync; + fpi_spi_transfer_unref; + fpi_spi_transfer_write; + fpi_spi_transfer_write_full; fpi_ssm_get_device; fpi_ssm_jump_to_state_delayed; fpi_ssm_mark_completed_delayed; fpi_ssm_new_full; fpi_ssm_next_state_delayed; - fpi_ssm_spi_*; + fpi_ssm_spi_transfer_cb; + fpi_ssm_spi_transfer_with_weak_pointer_cb; } LIBFPRINT_TOD_@tod_soversion@.0.0; LIBFPRINT_TOD_@tod_soversion@_1.94 { @@ -31,4 +243,7 @@ global: fpi_device_resume_complete; fpi_device_suspend_complete; fpi_ssm_silence_debug; + + /* Keep this always in the current development version */ + fpi_*; } LIBFPRINT_TOD_@tod_soversion@_1.92;