From 3f7a638eed88e24ed221bd3b08def3951a80e651 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 5 Jan 2021 15:59:07 +0100 Subject: [PATCH] virtual-device: Add non-image mock devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two variants one with storage and identify support and the other without storage. It implements the following commands: * INSERT id * REMOVE id * SCAN id * ERROR error-code * LIST (returns saved print) The INSERT/REMOVE/LIST commands are only available in the storage driver. The SCAN command emulates presenting a finger. These commands can be send ahead of time, and will be queued and processed when appropriate. i.e. for INSERT/REMOVE that is immediately when possible, for SCAN/ERROR processing is delayed. The LIST command is always processed immediately. Note that only a single command can be send per socket connection and the command must be send in a single message. The socket will be closed after the command has been processed. Co-authored-by: Bastien Nocera Co-authored-by: Marco Trevisan (TreviƱo) --- libfprint/drivers/virtual-device-storage.c | 181 +++++++++++ libfprint/drivers/virtual-device.c | 331 +++++++++++++++++++++ libfprint/meson.build | 6 + meson.build | 6 +- tests/meson.build | 6 +- tests/test-utils.c | 2 + tests/test-utils.h | 2 + 7 files changed, 532 insertions(+), 2 deletions(-) create mode 100644 libfprint/drivers/virtual-device-storage.c create mode 100644 libfprint/drivers/virtual-device.c diff --git a/libfprint/drivers/virtual-device-storage.c b/libfprint/drivers/virtual-device-storage.c new file mode 100644 index 0000000..aae0f33 --- /dev/null +++ b/libfprint/drivers/virtual-device-storage.c @@ -0,0 +1,181 @@ +/* + * Virtual driver for "simple" device debugging with storage + * + * Copyright (C) 2020 Bastien Nocera + * Copyright (C) 2020 Marco Trevisan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This is a virtual driver to debug the non-image based drivers. A small + * python script is provided to connect to it via a socket, allowing + * prints to registered programmatically. + * Using this, it is possible to test libfprint and fprintd. + */ + +#define FP_COMPONENT "virtual_device_storage" + +#include "virtual-device-private.h" +#include "fpi-log.h" + +G_DEFINE_TYPE (FpDeviceVirtualDeviceStorage, fpi_device_virtual_device_storage, fpi_device_virtual_device_get_type ()) + +static void +dev_identify (FpDevice *dev) +{ + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev); + GPtrArray *prints; + GError *error = NULL; + g_autofree char *scan_id = NULL; + + fpi_device_get_identify_data (dev, &prints); + + scan_id = process_cmds (self, TRUE, &error); + + if (scan_id) + { + GVariant *data = NULL; + FpPrint *new_scan; + FpPrint *match = NULL; + guint idx; + + new_scan = fp_print_new (dev); + fpi_print_set_type (new_scan, FPI_PRINT_RAW); + if (self->prints_storage) + fpi_print_set_device_stored (new_scan, TRUE); + data = g_variant_new_string (scan_id); + g_object_set (new_scan, "fpi-data", data, NULL); + + if (g_ptr_array_find_with_equal_func (prints, + new_scan, + (GEqualFunc) fp_print_equal, + &idx)) + match = g_ptr_array_index (prints, idx); + + fpi_device_identify_report (dev, + match, + new_scan, + NULL); + } + + fpi_device_identify_complete (dev, error); +} + +struct ListData +{ + FpDevice *dev; + GPtrArray *res; +}; + +static void +dev_list_insert_print (gpointer key, + gpointer value, + gpointer user_data) +{ + struct ListData *data = user_data; + FpPrint *print = fp_print_new (data->dev); + GVariant *var = NULL; + + fpi_print_fill_from_user_id (print, key); + fpi_print_set_type (print, FPI_PRINT_RAW); + var = g_variant_new_string (key); + g_object_set (print, "fpi-data", var, NULL); + g_object_ref_sink (print); + + g_ptr_array_add (data->res, print); +} + +static void +dev_list (FpDevice *dev) +{ + g_autoptr(GPtrArray) prints_list = NULL; + FpDeviceVirtualDevice *vdev = FP_DEVICE_VIRTUAL_DEVICE (dev); + struct ListData data; + + process_cmds (vdev, FALSE, NULL); + + prints_list = g_ptr_array_new_full (g_hash_table_size (vdev->prints_storage), NULL); + data.dev = dev; + data.res = prints_list; + + g_hash_table_foreach (vdev->prints_storage, dev_list_insert_print, &data); + + fpi_device_list_complete (dev, g_steal_pointer (&prints_list), NULL); +} + +static void +dev_delete (FpDevice *dev) +{ + g_autoptr(GVariant) data = NULL; + FpDeviceVirtualDevice *vdev = FP_DEVICE_VIRTUAL_DEVICE (dev); + FpPrint *print = NULL; + const char *id = NULL; + + process_cmds (vdev, FALSE, NULL); + + fpi_device_get_delete_data (dev, &print); + + g_object_get (print, "fpi-data", &data, NULL); + if (data == NULL) + { + fpi_device_delete_complete (dev, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + id = g_variant_get_string (data, NULL); + + fp_dbg ("Deleting print %s for user %s", + id, + fp_print_get_username (print)); + + if (g_hash_table_remove (vdev->prints_storage, id)) + fpi_device_delete_complete (dev, NULL); + else + fpi_device_delete_complete (dev, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); +} + +static void +fpi_device_virtual_device_storage_init (FpDeviceVirtualDeviceStorage *self) +{ + FpDeviceVirtualDevice *vdev = FP_DEVICE_VIRTUAL_DEVICE (self); + + vdev->prints_storage = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + NULL); +} + +static const FpIdEntry driver_ids[] = { + { .virtual_envvar = "FP_VIRTUAL_DEVICE_STORAGE" }, + { .virtual_envvar = "FP_VIRTUAL_DEVICE_IDENT" }, + { .virtual_envvar = NULL } +}; + +static void +fpi_device_virtual_device_storage_class_init (FpDeviceVirtualDeviceStorageClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Virtual device with storage and identification for debugging"; + dev_class->id_table = driver_ids; + + dev_class->identify = dev_identify; + dev_class->list = dev_list; + dev_class->delete = dev_delete; +} diff --git a/libfprint/drivers/virtual-device.c b/libfprint/drivers/virtual-device.c new file mode 100644 index 0000000..5c143b1 --- /dev/null +++ b/libfprint/drivers/virtual-device.c @@ -0,0 +1,331 @@ +/* + * Virtual driver for "simple" device debugging + * + * Copyright (C) 2019 Benjamin Berg + * Copyright (C) 2020 Bastien Nocera + * Copyright (C) 2020 Marco Trevisan + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This is a virtual driver to debug the non-image based drivers. A small + * python script is provided to connect to it via a socket, allowing + * prints to registered programmatically. + * Using this, it is possible to test libfprint and fprintd. + */ + +#define FP_COMPONENT "virtual_device" + +#include "virtual-device-private.h" +#include "fpi-log.h" + +G_DEFINE_TYPE (FpDeviceVirtualDevice, fpi_device_virtual_device, FP_TYPE_DEVICE) + +#define INSERT_CMD_PREFIX "INSERT " +#define REMOVE_CMD_PREFIX "REMOVE " +#define SCAN_CMD_PREFIX "SCAN " +#define ERROR_CMD_PREFIX "ERROR " + +#define LIST_CMD "LIST" + +char * +process_cmds (FpDeviceVirtualDevice * self, gboolean scan, GError * *error) +{ + while (self->pending_commands->len > 0) + { + gchar *cmd = g_ptr_array_index (self->pending_commands, 0); + + /* These are always processed. */ + if (g_str_has_prefix (cmd, INSERT_CMD_PREFIX)) + { + g_assert (self->prints_storage); + g_hash_table_add (self->prints_storage, + g_strdup (cmd + strlen (INSERT_CMD_PREFIX))); + + g_ptr_array_remove_index (self->pending_commands, 0); + continue; + } + else if (g_str_has_prefix (cmd, REMOVE_CMD_PREFIX)) + { + g_assert (self->prints_storage); + if (!g_hash_table_remove (self->prints_storage, + cmd + strlen (REMOVE_CMD_PREFIX))) + g_warning ("ID %s was not found in storage", cmd + strlen (REMOVE_CMD_PREFIX)); + + g_ptr_array_remove_index (self->pending_commands, 0); + continue; + } + + /* If we are not scanning, then we have to stop here. */ + if (!scan) + break; + + if (g_str_has_prefix (cmd, SCAN_CMD_PREFIX)) + { + char *res = g_strdup (cmd + strlen (SCAN_CMD_PREFIX)); + + g_ptr_array_remove_index (self->pending_commands, 0); + return res; + } + else if (g_str_has_prefix (cmd, ERROR_CMD_PREFIX)) + { + g_propagate_error (error, + fpi_device_error_new (g_ascii_strtoull (cmd + strlen (ERROR_CMD_PREFIX), NULL, 10))); + + g_ptr_array_remove_index (self->pending_commands, 0); + return NULL; + } + else + { + g_warning ("Could not process command: %s", cmd); + g_ptr_array_remove_index (self->pending_commands, 0); + } + } + + /* No commands left, throw a timeout error. */ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "No commands left that can be run!"); + return NULL; +} + +static void +write_key_to_listener (void *key, void *val, void *user_data) +{ + FpDeviceVirtualListener *listener = FP_DEVICE_VIRTUAL_LISTENER (user_data); + + if (!fp_device_virtual_listener_write_sync (listener, key, strlen (key), NULL) || + !fp_device_virtual_listener_write_sync (listener, "\n", 1, NULL)) + g_warning ("Error writing reply to LIST command"); +} + +static void +recv_instruction_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + FpDeviceVirtualListener *listener = FP_DEVICE_VIRTUAL_LISTENER (source_object); + gsize bytes; + + bytes = fp_device_virtual_listener_read_finish (listener, res, &error); + fp_dbg ("Got instructions of length %ld\n", bytes); + + if (error) + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + g_warning ("Error receiving instruction data: %s", error->message); + return; + } + + if (bytes > 0) + { + FpDeviceVirtualDevice *self; + g_autofree char *cmd = NULL; + + self = FP_DEVICE_VIRTUAL_DEVICE (user_data); + + cmd = g_strndup (self->recv_buf, bytes); + + if (g_str_has_prefix (cmd, LIST_CMD)) + { + if (self->prints_storage) + g_hash_table_foreach (self->prints_storage, write_key_to_listener, listener); + } + else + { + g_ptr_array_add (self->pending_commands, g_steal_pointer (&cmd)); + } + } + + fp_device_virtual_listener_connection_close (listener); +} + +static void +recv_instruction (FpDeviceVirtualDevice *self) +{ + fp_device_virtual_listener_read (self->listener, + FALSE, + self->recv_buf, + sizeof (self->recv_buf), + recv_instruction_cb, + self); +} + +static void +on_listener_connected (FpDeviceVirtualListener *listener, + gpointer user_data) +{ + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (user_data); + + recv_instruction (self); +} + +static void +dev_init (FpDevice *dev) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GCancellable) cancellable = NULL; + g_autoptr(FpDeviceVirtualListener) listener = NULL; + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev); + + G_DEBUG_HERE (); + + listener = fp_device_virtual_listener_new (); + cancellable = g_cancellable_new (); + + if (!fp_device_virtual_listener_start (listener, + fpi_device_get_virtual_env (FP_DEVICE (self)), + cancellable, + on_listener_connected, + self, + &error)) + { + fpi_device_open_complete (dev, g_steal_pointer (&error)); + return; + } + + self->listener = g_steal_pointer (&listener); + self->cancellable = g_steal_pointer (&cancellable); + + fpi_device_open_complete (dev, NULL); +} + +static void +dev_verify (FpDevice *dev) +{ + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev); + FpPrint *print; + GError *error = NULL; + g_autofree char *scan_id = NULL; + + fpi_device_get_verify_data (dev, &print); + + scan_id = process_cmds (self, TRUE, &error); + + if (scan_id) + { + GVariant *data = NULL; + FpPrint *new_scan; + gboolean success; + + g_debug ("Virtual device scanned print %s", scan_id); + + new_scan = fp_print_new (dev); + fpi_print_set_type (new_scan, FPI_PRINT_RAW); + if (self->prints_storage) + fpi_print_set_device_stored (new_scan, TRUE); + data = g_variant_new_string (scan_id); + g_object_set (new_scan, "fpi-data", data, NULL); + + success = fp_print_equal (print, new_scan); + + fpi_device_verify_report (dev, + success ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL, + new_scan, + NULL); + } + else + { + g_debug ("Virtual device scann failed with error: %s", error->message); + } + + fpi_device_verify_complete (dev, error); +} + +static void +dev_enroll (FpDevice *dev) +{ + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev); + GError *error = NULL; + FpPrint *print = NULL; + g_autofree char *id = NULL; + + fpi_device_get_enroll_data (dev, &print); + + id = process_cmds (self, TRUE, &error); + + if (id) + { + GVariant *data; + + fpi_print_set_type (print, FPI_PRINT_RAW); + data = g_variant_new_string (id); + g_object_set (print, "fpi-data", data, NULL); + + if (self->prints_storage) + { + g_hash_table_add (self->prints_storage, g_strdup (id)); + fpi_print_set_device_stored (print, TRUE); + } + + fpi_device_enroll_complete (dev, g_object_ref (print), error); + } + else + { + fpi_device_enroll_complete (dev, NULL, error); + } +} + +static void +dev_deinit (FpDevice *dev) +{ + FpDeviceVirtualDevice *self = FP_DEVICE_VIRTUAL_DEVICE (dev); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->listener); + g_clear_object (&self->listener); + + fpi_device_close_complete (dev, NULL); +} + +static void +fpi_device_virtual_device_finalize (GObject *object) +{ + G_DEBUG_HERE (); +} + +static void +fpi_device_virtual_device_init (FpDeviceVirtualDevice *self) +{ + self->pending_commands = g_ptr_array_new_with_free_func (g_free); +} + +static const FpIdEntry driver_ids[] = { + { .virtual_envvar = "FP_VIRTUAL_DEVICE", }, + { .virtual_envvar = NULL } +}; + +static void +fpi_device_virtual_device_class_init (FpDeviceVirtualDeviceClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = fpi_device_virtual_device_finalize; + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Virtual device for debugging"; + dev_class->type = FP_DEVICE_TYPE_VIRTUAL; + dev_class->id_table = driver_ids; + dev_class->nr_enroll_stages = 5; + + dev_class->open = dev_init; + dev_class->close = dev_deinit; + dev_class->verify = dev_verify; + dev_class->enroll = dev_enroll; +} diff --git a/libfprint/meson.build b/libfprint/meson.build index 5309391..0090ece 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -155,6 +155,12 @@ foreach driver: drivers if driver == 'virtual_image' drivers_sources += [ 'drivers/virtual-image.c' ] endif + if driver == 'virtual_device' + drivers_sources += [ 'drivers/virtual-device.c' ] + endif + if driver == 'virtual_device_storage' + drivers_sources += [ 'drivers/virtual-device-storage.c' ] + endif if driver.startswith('virtual_') drivers_sources += [ 'drivers/virtual-device-listener.c' ] endif diff --git a/meson.build b/meson.build index 8dc852a..2b6fe84 100644 --- a/meson.build +++ b/meson.build @@ -88,7 +88,11 @@ cairo_dep = dependency('cairo', required: false) # Drivers drivers = get_option('drivers').split(',') -virtual_drivers = [ 'virtual_image' ] +virtual_drivers = [ + 'virtual_image', + 'virtual_device', + 'virtual_device_storage', +] default_drivers = [ 'upektc_img', diff --git a/tests/meson.build b/tests/meson.build index 8e24980..61decd5 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -13,7 +13,11 @@ envs.prepend('LD_LIBRARY_PATH', join_paths(meson.build_root(), 'libfprint')) envs.set('FP_DEVICE_EMULATION', '1') # Set a colon-separated list of native drivers we enable in tests -envs.set('FP_DRIVERS_WHITELIST', 'virtual_image') +envs.set('FP_DRIVERS_WHITELIST', ':'.join([ + 'virtual_image', + 'virtual_device', + 'virtual_device_storage', +])) envs.set('NO_AT_BRIDGE', '1') diff --git a/tests/test-utils.c b/tests/test-utils.c index 1c1be17..a14d1ca 100644 --- a/tests/test-utils.c +++ b/tests/test-utils.c @@ -29,6 +29,8 @@ struct const char *device_id; } devtype_vars[FPT_NUM_VIRTUAL_DEVICE_TYPES] = { { "FP_VIRTUAL_IMAGE", "virtual_image", "virtual_image" }, /* FPT_VIRTUAL_DEVICE_IMAGE */ + { "FP_VIRTUAL_DEVICE", "virtual_device", "virtual_device" }, /* FPT_VIRTUAL_DEVICE_NONIMAGE */ + { "FP_VIRTUAL_DEVICE_STORAGE", "virtual_device_storage", "virtual_device_storage" } /* FPT_VIRTUAL_DEVICE_NONIMAGE_STORAGE */ }; static FptVirtualDeviceType global_devtype; diff --git a/tests/test-utils.h b/tests/test-utils.h index e4e4c30..7419a4c 100644 --- a/tests/test-utils.h +++ b/tests/test-utils.h @@ -21,6 +21,8 @@ typedef enum { FPT_VIRTUAL_DEVICE_IMAGE = 0, + FPT_VIRTUAL_DEVICE_NONIMAGE, + FPT_VIRTUAL_DEVICE_NONIMAGE_STORAGE, FPT_NUM_VIRTUAL_DEVICE_TYPES } FptVirtualDeviceType;