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.
This commit is contained in:
Dmitrii Shcherbakov
2021-08-29 14:18:29 +03:00
committed by Benjamin Berg
parent e198b04222
commit 999bca076c
9 changed files with 305 additions and 52 deletions

View File

@@ -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 ();

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;
/**

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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