diff --git a/README.md b/README.md index dbea943..6ce5f21 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,12 @@ ## Status: **WORKING** ✓ -Enrollment and verify confirmed working on real hardware (2026-03-10). +Enrollment and verify confirmed working on real hardware (2026-05-26). ## Hardware -[TNP Nano USB Fingerprint Reader](https://www.amazon.com/dp/B07DW62XS7) (Amazon) +[TNP Nano USB Fingerprint Reader](https://www.amazon.com/dp/B07DW62XS7) (Amazon US) +[TNP Nano USB Fingerprint Reader](https://www.amazon.co.uk/TNP-Fingerprint-Reader-Windows-Hello/dp/B07DW62XS7/) (Amazon UK) TNP Nano USB Fingerprint Reader @@ -45,15 +46,71 @@ See [Reverse Engineering](docs/reverse-engineering.md) for how to reproduce. - The required number of GenChar samples for RegModel is `DAT_180032020 / 3`clamped to \[3, 6\]. For this device the value is 6. - Extra GET_IMAGE calls between GenChars corrupt the device's char buffer state. The `waiting_for_lift` approach must minimize GET_IMAGE polling between captures. -## Build +## Instructions +1. Plug in device & update your machine ```bash -./build.sh +sudo apt update ``` -Copies `src/microarray.c` into the libfprint source tree (`$LIBFPRINT_SRC`, defaults to `~/libfprint`), builds with ninja, and installs the shared library. +2. Install all required tools/libraries + +```bash +sudo apt install -y git build-essential meson ninja-build \ + pkg-config libglib2.0-dev libgusb-dev libgudev-1.0-dev \ + libpixman-1-dev libnss3-dev libssl-dev libcairo2-dev \ + libgirepository1.0-dev gtk-doc-tools \ + fprintd libpam-fprintd + ``` +If you are on Ubuntu 25.4 or later you also need to run ```sudo apt install systemd-dev``` + +3. Clone the official libfprint source code to ~/libfprint directory + +```bash +rm -rf ~/libfprint +git clone https://gitlab.freedesktop.org/libfprint/libfprint.git ~/libfprint +``` + +4. Manually create the microarray directory + +```bash +cd ~/libfprint +mkdir -p libfprint/drivers/microarray +meson setup build +``` + +5. Copy this repo + +```bash +rm -rf ~/libfprint-microarray/ +git clone https://github.com/jadegamesuk/libfprint-microarray.git ~/libfprint-microarray +``` + +6. Make changes to ~/libfprint + +```bash +# copy fixed meson.build file over to other library +cp ~/libfprint-microarray/meson.build ~/libfprint/libfprint/meson.build +cp ~/libfprint-microarray/src/microarray.c ~/libfprint/libfprint/drivers/microarray/microarray.c + +cd ~/libfprint/build +meson configure -Ddrivers=all +ninja +sudo cp libfprint/libfprint-2.so.2.0.0 /usr/lib/x86_64-linux-gnu/libfprint-2.so.2 +sudo cp libfprint/libfprint-2.so.2.0.0 /usr/lib/libfprint-2.so.2 +sudo ldconfig +sudo udevadm control --reload-rules && sudo udevadm trigger +#sudo systemctl enable --now fprintd +``` ## Testing +```bash +# Restart daemon +sudo systemctl stop fprintd +sudo G_MESSAGES_DEBUG=all /usr/libexec/fprintd -t 2>&1 +``` + +### Open a new Terminal Window for the following ```bash # Enroll right index finger (6 press/lift cycles) @@ -62,10 +119,23 @@ fprintd-enroll -f right-index-finger # Verify fprintd-verify -f right-index-finger -# Debug logging -sudo G_MESSAGES_DEBUG=all /usr/libexec/fprintd -t 2>&1 +# Options for [finger]: +left-thumb +left-index-finger +left-middle-finger +left-ring-finger +left-little-finger +right-thumb +right-index-finger +right-middle-finger +right-ring-finger +right-little-finger ``` +**If there is a future update of libfprint, re-running Step (6) above should make everything work again.** + + + ## License [MIT](LICENSE) diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..32ed7dd --- /dev/null +++ b/meson.build @@ -0,0 +1,425 @@ +spi_sources = [] +spi_headers = [] + +if enabled_spi_drivers.length() > 0 + spi_headers = ['fpi-spi-transfer.h'] + spi_sources = ['fpi-spi-transfer.c'] +endif + +libfprint_sources = [ + 'fp-context.c', + 'fp-device.c', + 'fp-image.c', + 'fp-print.c', + 'fp-image-device.c', +] + +libfprint_private_sources = [ + 'fpi-assembling.c', + 'fpi-byte-reader.c', + 'fpi-byte-writer.c', + 'fpi-device.c', + 'fpi-image-device.c', + 'fpi-image.c', + 'fpi-print.c', + 'fpi-ssm.c', + 'fpi-usb-transfer.c', +] + spi_sources + +libfprint_public_headers = [ + 'fp-context.h', + 'fp-device.h', + 'fp-image-device.h', + 'fp-image.h', + 'fp-print.h', +] + +libfprint_private_headers = [ + 'fpi-assembling.h', + 'fpi-byte-reader.h', + 'fpi-byte-utils.h', + 'fpi-byte-writer.h', + 'fpi-compat.h', + 'fpi-context.h', + 'fpi-device.h', + 'fpi-image-device.h', + 'fpi-image.h', + 'fpi-log.h', + 'fpi-minutiae.h', + 'fpi-print.h', + 'fpi-usb-transfer.h', + 'fpi-ssm.h', +] + spi_headers + +nbis_sources = [ + 'nbis/bozorth3/bozorth3.c', + 'nbis/bozorth3/bz_alloc.c', + 'nbis/bozorth3/bz_drvrs.c', + 'nbis/bozorth3/bz_gbls.c', + 'nbis/bozorth3/bz_io.c', + 'nbis/bozorth3/bz_sort.c', + 'nbis/mindtct/binar.c', + 'nbis/mindtct/block.c', + 'nbis/mindtct/chaincod.c', + 'nbis/mindtct/contour.c', + 'nbis/mindtct/detect.c', + 'nbis/mindtct/dft.c', + 'nbis/mindtct/free.c', + 'nbis/mindtct/getmin.c', + 'nbis/mindtct/globals.c', + 'nbis/mindtct/imgutil.c', + 'nbis/mindtct/init.c', + 'nbis/mindtct/line.c', + 'nbis/mindtct/link.c', + 'nbis/mindtct/log.c', + 'nbis/mindtct/loop.c', + 'nbis/mindtct/maps.c', + 'nbis/mindtct/matchpat.c', + 'nbis/mindtct/minutia.c', + 'nbis/mindtct/morph.c', + 'nbis/mindtct/quality.c', + 'nbis/mindtct/remove.c', + 'nbis/mindtct/ridges.c', + 'nbis/mindtct/shape.c', + 'nbis/mindtct/sort.c', + 'nbis/mindtct/util.c', + 'nbis/mindtct/xytreps.c', +] + +driver_sources = { + 'upekts' : + [ 'drivers/upekts.c', 'drivers/upek_proto.c' ], + 'upektc' : + [ 'drivers/upektc.c' ], + 'upeksonly' : + [ 'drivers/upeksonly.c' ], + 'uru4000' : + [ 'drivers/uru4000.c' ], + 'aes1610' : + [ 'drivers/aes1610.c' ], + 'aes1660' : + [ 'drivers/aes1660.c' ], + 'aes2501' : + [ 'drivers/aes2501.c' ], + 'aes2550' : + [ 'drivers/aes2550.c' ], + 'aes2660' : + [ 'drivers/aes2660.c' ], + 'aes3500' : + [ 'drivers/aes3500.c' ], + 'aes4000' : + [ 'drivers/aes4000.c' ], + 'vcom5s' : + [ 'drivers/vcom5s.c' ], + 'vfs101' : + [ 'drivers/vfs101.c' ], + 'vfs301' : + [ 'drivers/vfs301.c', 'drivers/vfs301_proto.c' ], + 'vfs5011' : + [ 'drivers/vfs5011.c' ], + 'vfs7552' : + [ 'drivers/vfs7552.c' ], + 'upektc_img' : + [ 'drivers/upektc_img.c', 'drivers/upek_proto.c' ], + 'etes603' : + [ 'drivers/etes603.c' ], + 'egis0570' : + [ 'drivers/egis0570.c' ], + 'egismoc' : + [ 'drivers/egismoc/egismoc.c' ], + 'vfs0050' : + [ 'drivers/vfs0050.c' ], + 'elan' : + [ 'drivers/elan.c' ], + 'elanmoc' : + [ 'drivers/elanmoc/elanmoc.c' ], + 'elanspi' : + [ 'drivers/elanspi.c' ], + 'nb1010' : + [ 'drivers/nb1010.c' ], + 'virtual_image' : + [ 'drivers/virtual-image.c' ], + 'virtual_device' : + [ 'drivers/virtual-device.c' ], + 'virtual_device_storage' : + [ 'drivers/virtual-device-storage.c' ], + 'synaptics' : + [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ], + 'goodixmoc' : + [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], + 'fpcmoc' : + [ 'drivers/fpcmoc/fpc.c' ], + 'realtek' : + [ 'drivers/realtek/realtek.c' ], + 'focaltech_moc' : + [ 'drivers/focaltech_moc/focaltech_moc.c' ], + 'microarray' : + [ 'drivers/microarray/microarray.c' ], +} + +helper_sources = { + 'aeslib' : + [ 'drivers/aeslib.c' ], + 'aesx660' : + [ 'drivers/aesx660.c' ], + 'aes3k' : + [ 'drivers/aes3k.c' ], + 'openssl' : + [ ], + 'udev' : + [ ], + 'virtual' : + [ 'drivers/virtual-device-listener.c' ], +} + +drivers_sources = [] +drivers_cflags = [] +foreach driver: drivers + drivers_sources += driver_sources[driver] +endforeach + +drivers_sources += driver_sources['microarray'] +foreach helper : driver_helpers + drivers_sources += helper_sources[helper] +endforeach + + +fp_enums = gnome.mkenums_simple('fp-enums', + sources: libfprint_public_headers, + install_header: true, + install_dir: get_option('includedir') / versioned_libname, +) +fp_enums_h = fp_enums[1] + +fpi_enums = gnome.mkenums_simple('fpi-enums', + sources: libfprint_private_headers, + install_header: false, +) +fpi_enums_h = fpi_enums[1] + +enums_dep = declare_dependency( + sources: [ fp_enums_h, fpi_enums_h ] +) + +# Export the drivers' types to the core code +drivers_type_list = [] +drivers_type_func = [] +drivers_type_list += '#include ' +drivers_type_list += '#include "fpi-context.h"' +drivers_type_list += '' +drivers_type_func += 'GArray *' +drivers_type_func += 'fpi_get_driver_types (void)' +drivers_type_func += '{' +drivers_type_func += ' GArray *drivers = g_array_new (TRUE, FALSE, sizeof (GType));' +drivers_type_func += ' GType t;' +drivers_type_func += '' + +supported_drivers += 'microarray' + +foreach driver: supported_drivers + drivers_type_list += 'extern GType (fpi_device_' + driver + '_get_type) (void);' + drivers_type_func += ' t = fpi_device_' + driver + '_get_type ();' + drivers_type_func += ' g_array_append_val (drivers, t);' + drivers_type_func += '' +endforeach +drivers_type_list += '' +drivers_type_func += ' return drivers;' +drivers_type_func += '}' + +drivers_sources += configure_file(input: 'empty_file', + output: 'fpi-drivers.c', + capture: true, + command: [ + 'echo', + '\n'.join(drivers_type_list + [] + drivers_type_func) + ]) + +deps = [ + enums_dep, + gio_dep, + glib_dep, + gobject_dep, + gusb_dep, + mathlib_dep, +] + optional_deps + +# These are empty and only exist so that the include directories are created +# in the build tree. This silences a build time warning. +subdir('nbis/include') +subdir('nbis/libfprint-include') +deps += declare_dependency(include_directories: [ + root_inc, + include_directories('nbis/include'), + include_directories('nbis/libfprint-include'), +]) + +libnbis = static_library('nbis', + nbis_sources, + dependencies: deps, + c_args: cc.get_supported_arguments([ + '-Wno-error=redundant-decls', + '-Wno-redundant-decls', + '-Wno-discarded-qualifiers', + '-Wno-array-bounds', + '-Wno-array-parameter', + '-Wno-unused-but-set-variable', + ]), + install: false) + +libfprint_private = static_library('fprint-private', + sources: [ + fpi_enums, + libfprint_private_sources, + ], + dependencies: deps, + link_with: libnbis, + install: false) + +libfprint_drivers = static_library('fprint-drivers', + sources: drivers_sources, + c_args: drivers_cflags, + dependencies: deps, + link_with: libfprint_private, + install: false) + +mapfile = files('libfprint.ver')[0] +if meson.version().version_compare('>=1.4') + mapfile_path = mapfile.full_path() +else + mapfile_path = meson.project_source_root() / '@0@'.format(mapfile) +endif +vflag = '-Wl,--version-script,@0@'.format(mapfile_path) + +libfprint = shared_library(versioned_libname.split('lib')[1], + sources: [ + fp_enums, + libfprint_sources, + ], + soversion: soversion, + version: libversion, + link_args : vflag, + link_depends : mapfile, + link_with: [libfprint_drivers, libfprint_private], + dependencies: deps, + install: true) + +libfprint_dep = declare_dependency(link_with: libfprint, + include_directories: root_inc, + dependencies: [ + enums_dep, + gio_dep, + glib_dep, + gobject_dep, + gusb_dep, + ]) + +install_headers(['fprint.h'] + libfprint_public_headers, + subdir: versioned_libname +) + +libfprint_private_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libfprint_private, + dependencies: [ + deps, + libfprint_dep, + ] +) + +udev_hwdb = executable('fprint-list-udev-hwdb', + 'fprint-list-udev-hwdb.c', + dependencies: libfprint_private_dep, + link_with: libfprint_drivers, + install: false) + +udev_hwdb_generator = custom_target('udev-hwdb', + output: 'autosuspend.hwdb', + depend_files: drivers_sources, + capture: true, + command: [ udev_hwdb ], + install: false, +) + +metainfo = executable('fprint-list-metainfo', + 'fprint-list-metainfo.c', + dependencies: libfprint_private_dep, + link_with: libfprint_drivers, + install: false) + +metainfo_generator = custom_target('metainfo', + output: 'org.freedesktop.libfprint.metainfo.xml', + depend_files: drivers_sources, + capture: true, + command: [ metainfo ], + install: true, + install_dir: datadir / 'metainfo' +) + +if install_udev_rules + udev_rules = executable('fprint-list-udev-rules', + 'fprint-list-udev-rules.c', + dependencies: libfprint_private_dep, + link_with: libfprint_drivers, + install: false) + + custom_target('udev-rules', + output: '70-@0@.rules'.format(versioned_libname), + depend_files: drivers_sources, + capture: true, + command: [ udev_rules ], + install: true, + install_dir: udev_rules_dir, + ) +endif + +sync_udev_udb = custom_target('sync-udev-hwdb', + depends: udev_hwdb_generator, + output: 'sync-udev-hwdb', + install: false, + command: [ + 'cp', '-v', + udev_hwdb_generator.full_path(), + meson.project_source_root() / 'data' + ] +) + +alias_target('sync-udev-hwdb', sync_udev_udb) + +supported_devices = executable('fprint-list-supported-devices', + 'fprint-list-supported-devices.c', + dependencies: libfprint_private_dep, + link_with: libfprint_drivers, + install: false) + + +if get_option('introspection') + # We do *not* include the private header here + libfprint_girtarget = gnome.generate_gir(libfprint, + sources : fp_enums + [ + libfprint_public_headers, + libfprint_sources, + ], + nsversion : '@0@.0'.format(soversion), + namespace : 'FPrint', + symbol_prefix : 'fp_', + identifier_prefix : 'Fp', + export_packages : 'fprint', + extra_args : [ + '--c-include=fprint.h', + ], + link_with : libfprint, + dependencies : [ + gio_dep, + gobject_dep, + gusb_dep, + ], + includes : [ + 'Gio-2.0', + 'GObject-2.0', + 'GUsb-1.0', + ], + fatal_warnings: true, + install : true) + libfprint_gir = libfprint_girtarget[0] + libfprint_typelib = libfprint_girtarget[1] +endif diff --git a/src/microarray.c b/src/microarray.c index d85c6c2..5fca54a 100644 --- a/src/microarray.c +++ b/src/microarray.c @@ -7,9 +7,9 @@ * protocol (same packet framing as the R30X hobbyist module series). * * Endpoints: - * EP 0x03 OUT bulk — commands to device - * EP 0x83 IN bulk — responses from device - * EP 0x82 IN intr — finger-detect events (used for waiting) + * EP 0x03 OUT bulk — commands to device + * EP 0x83 IN bulk — responses from device + * EP 0x82 IN intr — finger-detect events (used for waiting) * * Copyright (C) 2024 * SPDX-License-Identifier: LGPL-2.1-or-later @@ -74,6 +74,7 @@ struct _FpiDeviceMicroarray guint8 *resp_buf; /* allocated response buffer */ GCancellable *interrupt_cancellable; gboolean waiting_for_lift; /* TRUE after each successful capture */ + guint identify_index; }; G_DECLARE_FINAL_TYPE (FpiDeviceMicroarray, fpi_device_microarray, @@ -84,41 +85,21 @@ G_DEFINE_TYPE (FpiDeviceMicroarray, fpi_device_microarray, FP_TYPE_DEVICE) * Packet helpers * -------------------------------------------------------------------------- */ -/* - * Build a framed FPC command packet. - * Returns a newly allocated guint8 buffer (caller must g_free). - * Sets *out_len to the total packet length. - * - * Format: - * EF 01 FF FF FF FF [type=01] [len_hi] [len_lo] [cmd_bytes...] [csum_hi] [csum_lo] - * where len = cmd_len + 2 (payload + 2-byte checksum) - */ static guint8 * ma_build_cmd (const guint8 *cmd, gsize cmd_len, gsize *out_len) { gsize total = MA_OVERHEAD + cmd_len + 2; /* 2 checksum bytes */ guint8 *pkt = g_malloc (total); - /* sync + address */ - pkt[0] = 0xEF; - pkt[1] = 0x01; - pkt[2] = 0xFF; - pkt[3] = 0xFF; - pkt[4] = 0xFF; - pkt[5] = 0xFF; - - /* packet type */ + pkt[0] = 0xEF; pkt[1] = 0x01; pkt[2] = 0xFF; pkt[3] = 0xFF; pkt[4] = 0xFF; pkt[5] = 0xFF; pkt[6] = MA_PKT_CMD; - /* length = cmd_len + 2 (big-endian) */ guint16 len = (guint16)(cmd_len + 2); pkt[7] = (guint8)(len >> 8); pkt[8] = (guint8)(len & 0xFF); - /* command payload */ memcpy (pkt + 9, cmd, cmd_len); - /* 16-bit checksum: sum of bytes [6 .. 9+cmd_len-1] */ guint16 csum = 0; for (gsize i = 6; i < 9 + cmd_len; i++) csum += pkt[i]; @@ -129,50 +110,40 @@ ma_build_cmd (const guint8 *cmd, gsize cmd_len, gsize *out_len) return pkt; } -/* - * Parse an FPC response packet. - * Returns TRUE if header/checksum valid; resp_data and resp_data_len - * point into buf (not copied). - */ -static gboolean +/* Suppressed unused warning so the compiler doesn't fail on -Werror */ +static G_GNUC_UNUSED gboolean ma_parse_resp (const guint8 *buf, gsize buf_len, const guint8 **data_out, gsize *data_len_out, GError **error) { if (buf_len < (gsize)(MA_OVERHEAD + 2)) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Response too short"); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Response too short"); return FALSE; } if (buf[0] != 0xEF || buf[1] != 0x01) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Bad sync header"); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Bad sync header"); return FALSE; } if (buf[6] != MA_PKT_ACK) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Expected ACK (0x07), got 0x%02x", buf[6]); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Expected ACK (0x07), got 0x%02x", buf[6]); return FALSE; } guint16 len = ((guint16)buf[7] << 8) | buf[8]; gsize expected = (gsize)MA_OVERHEAD + len; if (buf_len < expected) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Response truncated"); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Response truncated"); return FALSE; } - /* verify checksum: sum of bytes [6 .. expected-3] */ guint16 csum = 0; for (gsize i = 6; i < expected - 2; i++) csum += buf[i]; guint16 got = ((guint16)buf[expected-2] << 8) | buf[expected-1]; if (csum != got) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Checksum mismatch: want 0x%04x got 0x%04x", csum, got); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Checksum mismatch"); return FALSE; } *data_out = buf + MA_OVERHEAD; - *data_len_out = (gsize)(len - 2); /* strip 2 checksum bytes */ + *data_len_out = (gsize)(len - 2); return TRUE; } @@ -187,29 +158,23 @@ enum { }; static void -init_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, - gpointer user_data, GError *error) +init_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpiSsm *ssm = user_data; if (error) { fpi_ssm_mark_failed (ssm, error); return; } - /* Basic handshake response check: just verify header bytes */ - if (transfer->actual_length >= 2 && - transfer->buffer[0] == 0xEF && transfer->buffer[1] == 0x01) { + if (transfer->actual_length >= 2 && transfer->buffer[0] == 0xEF && transfer->buffer[1] == 0x01) { fp_dbg ("Handshake OK"); fpi_ssm_mark_completed (ssm); } else { - fpi_ssm_mark_failed (ssm, - fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "Handshake response invalid")); + fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, "Handshake response invalid")); } } static void -init_send_cb (FpiUsbTransfer *transfer, FpDevice *device, - gpointer user_data, GError *error) +init_send_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpiSsm *ssm = user_data; if (error) { @@ -222,26 +187,21 @@ init_send_cb (FpiUsbTransfer *transfer, FpDevice *device, static void init_run_state (FpiSsm *ssm, FpDevice *device) { - FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); FpiUsbTransfer *transfer; - switch (fpi_ssm_get_cur_state (ssm)) { case INIT_SEND_HANDSHAKE: { guint8 *buf = g_memdup2 (MA_HANDSHAKE_PKT, MA_HANDSHAKE_PKT_LEN); transfer = fpi_usb_transfer_new (device); transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_OUT, - buf, MA_HANDSHAKE_PKT_LEN, g_free); - fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, NULL, - init_send_cb, ssm); + fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_OUT, buf, MA_HANDSHAKE_PKT_LEN, g_free); + fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), init_send_cb, ssm); break; } case INIT_RECV_HANDSHAKE: transfer = fpi_usb_transfer_new (device); transfer->ssm = ssm; fpi_usb_transfer_fill_bulk (transfer, MA_EP_IN, MA_HANDSHAKE_RESP_LEN); - fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, NULL, - init_recv_cb, ssm); + fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), init_recv_cb, ssm); break; default: g_assert_not_reached (); @@ -258,13 +218,10 @@ static void ma_dev_open (FpDevice *device) { GError *error = NULL; - - if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), - 0, 0, &error)) { + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) { fpi_device_open_complete (device, error); return; } - FpiSsm *ssm = fpi_ssm_new (device, init_run_state, INIT_NUM_STATES); fpi_ssm_start (ssm, init_ssm_done); } @@ -273,8 +230,7 @@ static void ma_dev_close (FpDevice *device) { GError *error = NULL; - g_usb_device_release_interface (fpi_device_get_usb_device (device), - 0, 0, &error); + g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error); fpi_device_close_complete (device, error); } @@ -282,15 +238,8 @@ ma_dev_close (FpDevice *device) * Generic command send/receive helpers * -------------------------------------------------------------------------- */ -/* - * Sends a framed command and reads back the response into self->resp_buf. - * On completion, calls fpi_ssm_next_state (ssm). - * The caller is responsible for checking self->resp_buf[0] afterward. - */ - static void -cmd_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, - gpointer user_data, GError *error) +cmd_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpiSsm *ssm = user_data; if (error) { @@ -301,8 +250,7 @@ cmd_recv_cb (FpiUsbTransfer *transfer, FpDevice *device, } static void -cmd_send_cb (FpiUsbTransfer *transfer, FpDevice *device, - gpointer user_data, GError *error) +cmd_send_cb (FpiUsbTransfer *transfer, FpDevice *device, gpointer user_data, GError *error) { FpiSsm *ssm = user_data; if (error) { @@ -312,58 +260,37 @@ cmd_send_cb (FpiUsbTransfer *transfer, FpDevice *device, fpi_ssm_next_state (ssm); } -/* Submit a bulk-OUT command */ static void -ma_submit_cmd (FpiSsm *ssm, FpDevice *device, - const guint8 *cmd, gsize cmd_len) +ma_submit_cmd (FpiSsm *ssm, FpDevice *device, const guint8 *cmd, gsize cmd_len) { gsize pkt_len; guint8 *pkt = ma_build_cmd (cmd, cmd_len, &pkt_len); FpiUsbTransfer *transfer = fpi_usb_transfer_new (device); transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_OUT, - pkt, pkt_len, g_free); - fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, NULL, - cmd_send_cb, ssm); + fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_OUT, pkt, pkt_len, g_free); + fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), cmd_send_cb, ssm); } -/* Submit a bulk-IN read into self->resp_buf */ static void ma_submit_recv (FpiSsm *ssm, FpDevice *device, gsize expect_len) { FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); FpiUsbTransfer *transfer = fpi_usb_transfer_new (device); transfer->ssm = ssm; - /* Point transfer buffer at our persistent resp_buf */ - fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_IN, - self->resp_buf, expect_len, NULL); - fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, NULL, - cmd_recv_cb, ssm); + fpi_usb_transfer_fill_bulk_full (transfer, MA_EP_IN, self->resp_buf, expect_len, NULL); + fpi_usb_transfer_submit (transfer, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), cmd_recv_cb, ssm); } /* -------------------------------------------------------------------------- * Enroll state machine - * -------------------------------------------------------------------------- - * - * Pre-enrollment: - * ENROLL_HANDSHAKE / RECV — reset device session - * ENROLL_READ_INDEX_PRE / RECV — CMD 0x1F: read FID bitmap - * ENROLL_EMPTY / RECV — CMD 0x0D: only if all 30 slots full - * - * Loop MA_ENROLL_SAMPLES times: - * ENROLL_GET_IMAGE / RECV — CMD 0x01: poll until finger present - * ENROLL_GEN_CHAR / RECV — CMD 0x02: extract features into char buffer - * - * Complete: - * ENROLL_REG_MODEL / RECV — CMD 0x05: merge char buffers into template - * ENROLL_STORE_CHAR / RECV — CMD 0x06: store to self->fid (set in pre-check) - */ + * -------------------------------------------------------------------------- */ + enum { - ENROLL_HANDSHAKE, /* session reset — must call before every enrollment */ + ENROLL_HANDSHAKE, ENROLL_RECV_HANDSHAKE, - ENROLL_READ_INDEX_PRE, /* CMD 0x1F — find free FID slot before starting */ + ENROLL_READ_INDEX_PRE, ENROLL_RECV_READ_INDEX_PRE, - ENROLL_EMPTY, /* CMD 0x0D — only if no free slot found */ + ENROLL_EMPTY, ENROLL_RECV_EMPTY, ENROLL_GET_IMAGE, ENROLL_RECV_IMAGE, @@ -390,134 +317,90 @@ enroll_run_state (FpiSsm *ssm, FpDevice *device) guint8 cmd[8]; switch (fpi_ssm_get_cur_state (ssm)) { - case ENROLL_HANDSHAKE: { guint8 *buf = g_memdup2 (MA_HANDSHAKE_PKT, MA_HANDSHAKE_PKT_LEN); FpiUsbTransfer *t = fpi_usb_transfer_new (device); t->ssm = ssm; - fpi_usb_transfer_fill_bulk_full (t, MA_EP_OUT, - buf, MA_HANDSHAKE_PKT_LEN, g_free); - fpi_usb_transfer_submit (t, MA_TIMEOUT_CMD, NULL, cmd_send_cb, ssm); + fpi_usb_transfer_fill_bulk_full (t, MA_EP_OUT, buf, MA_HANDSHAKE_PKT_LEN, g_free); + fpi_usb_transfer_submit (t, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), cmd_send_cb, ssm); break; } - case ENROLL_RECV_HANDSHAKE: { FpiUsbTransfer *t = fpi_usb_transfer_new (device); t->ssm = ssm; fpi_usb_transfer_fill_bulk (t, MA_EP_IN, MA_HANDSHAKE_RESP_LEN); - fpi_usb_transfer_submit (t, MA_TIMEOUT_CMD, NULL, cmd_recv_cb, ssm); + fpi_usb_transfer_submit (t, MA_TIMEOUT_CMD, fpi_device_get_cancellable (device), cmd_recv_cb, ssm); break; } - case ENROLL_READ_INDEX_PRE: - cmd[0] = MA_CMD_READ_INDEX; - cmd[1] = 0x00; + cmd[0] = MA_CMD_READ_INDEX; cmd[1] = 0x00; ma_submit_cmd (ssm, device, cmd, 2); break; - case ENROLL_RECV_READ_INDEX_PRE: ma_submit_recv (ssm, device, MA_OVERHEAD + 35 + 2); break; - case ENROLL_EMPTY: { - /* Check bitmap from pre-enrollment ReadIndex */ const guint8 *resp = self->resp_buf + MA_OVERHEAD; + self->fid = -1; if (resp[0] == 0x00) { for (int byte = 0; byte < 4 && self->fid < 0; byte++) { for (int bit = 0; bit < 8; bit++) { + int candidate_slot = byte * 8 + bit; + if (candidate_slot > 9) break; if (!(resp[1 + byte] & (1 << bit))) { - self->fid = byte * 8 + bit; + self->fid = candidate_slot; break; } } } } - if (self->fid >= 0) { - fp_dbg ("FID slot %d free, skipping Empty", self->fid); + if (self->fid >= 0 && self->fid <= 9) { + fp_dbg ("Found free FID slot %d. Proceeding to image capture.", self->fid); fpi_ssm_jump_to_state (ssm, ENROLL_GET_IMAGE); return; } - /* No free slots — erase all templates, use slot 0 */ - fp_dbg ("no free FID slots, clearing all templates (CMD 0x0D)"); - self->fid = 0; - cmd[0] = MA_CMD_EMPTY; + fp_dbg ("Storage slots 0-9 full! Clearing all templates."); + self->fid = 0; cmd[0] = MA_CMD_EMPTY; ma_submit_cmd (ssm, device, cmd, 1); break; } - case ENROLL_RECV_EMPTY: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - case ENROLL_GET_IMAGE: cmd[0] = MA_CMD_GET_IMAGE; ma_submit_cmd (ssm, device, cmd, 1); break; - case ENROLL_RECV_IMAGE: - /* Response: 3 bytes payload, resp[0]=0 means image captured */ ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - case ENROLL_GEN_CHAR: if (self->resp_buf[MA_OVERHEAD] != 0x00) { - /* No image (finger absent or sensor not ready) */ if (self->waiting_for_lift) { - /* Finger was just lifted — ready for next press */ self->waiting_for_lift = FALSE; fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); fp_dbg ("Finger lifted, waiting for next press"); } - fp_dbg ("GetImage not ready (0x%02x), retrying", - self->resp_buf[MA_OVERHEAD]); g_timeout_add (100, poll_get_image_cb, ssm); return; } if (self->waiting_for_lift) { - /* Finger still down from previous capture — keep waiting */ - fp_dbg ("Waiting for finger lift..."); g_timeout_add (100, poll_get_image_cb, ssm); return; } - /* New finger press with valid image — proceed */ fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); - cmd[0] = MA_CMD_GEN_CHAR; - cmd[1] = (guint8)(self->enroll_stage + 1); + cmd[0] = MA_CMD_GEN_CHAR; cmd[1] = (guint8)(self->enroll_stage + 1); ma_submit_cmd (ssm, device, cmd, 2); break; - - case ENROLL_RECV_GEN_CHAR: { + case ENROLL_RECV_GEN_CHAR: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); - /* Note: actual check happens in the callback; we use a simple approach - * of checking after state transitions */ - /* Advance stage in next state entry */ break; - } - - /* We need to handle the stage increment after recv; do it via a dummy state - * by just checking here at the start of the NEXT state. But since we can't - * do that cleanly without extra states, we check at GEN_CHAR entry above. - * - * For stage counting: we increment after a successful GEN_CHAR recv. - * Since recv state just does the USB and moves to next state, we increment - * in REG_MODEL entry until stages are done. - * - * Simpler: jump back to GET_IMAGE if stage < SAMPLES, else fall through. - * We do the increment and check at ENROLL_REG_MODEL entry. */ - case ENROLL_REG_MODEL: - /* Check GenChar result and count sample */ - fp_dbg ("GenChar resp[0]=0x%02x stage=%d", - self->resp_buf[MA_OVERHEAD], self->enroll_stage); if (self->resp_buf[MA_OVERHEAD] == 0x00) { self->enroll_stage++; - fp_dbg ("stage %d / %d OK", self->enroll_stage, MA_ENROLL_SAMPLES); fpi_device_enroll_progress (device, self->enroll_stage, NULL, NULL); } else { - g_warning ("microarray: GenChar failed (0x%02x), retrying stage", - self->resp_buf[MA_OVERHEAD]); - fpi_device_enroll_progress (device, self->enroll_stage, NULL, - fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + fpi_device_enroll_progress (device, self->enroll_stage, NULL, fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); self->waiting_for_lift = TRUE; fpi_ssm_jump_to_state (ssm, ENROLL_GET_IMAGE); return; @@ -527,40 +410,24 @@ enroll_run_state (FpiSsm *ssm, FpDevice *device) fpi_ssm_jump_to_state (ssm, ENROLL_GET_IMAGE); return; } - fp_dbg ("all samples collected, sending RegModel (CMD 0x05)"); cmd[0] = MA_CMD_REG_MODEL; ma_submit_cmd (ssm, device, cmd, 1); break; - case ENROLL_RECV_REG_MODEL: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - case ENROLL_STORE_CHAR: - /* Check RegModel result */ - fp_dbg ("RegModel resp[0]=0x%02x, storing to FID slot %d", - self->resp_buf[MA_OVERHEAD], self->fid); if (self->resp_buf[MA_OVERHEAD] != 0x00) { - g_warning ("microarray: RegModel FAILED with 0x%02x", - self->resp_buf[MA_OVERHEAD]); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, - "RegModel failed: 0x%02x", - self->resp_buf[MA_OVERHEAD])); + fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "RegModel failed")); return; } - /* self->fid was set during pre-enrollment ReadIndex or after Empty */ - cmd[0] = MA_CMD_STORE_CHAR; - cmd[1] = 0x01; - cmd[2] = (guint8)(self->fid >> 8); - cmd[3] = (guint8)(self->fid & 0xFF); + cmd[0] = MA_CMD_STORE_CHAR; cmd[1] = 0x01; + cmd[2] = (guint8)(self->fid >> 8); cmd[3] = (guint8)(self->fid & 0xFF); ma_submit_cmd (ssm, device, cmd, 4); break; - case ENROLL_RECV_STORE_CHAR: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - default: g_assert_not_reached (); } @@ -570,20 +437,14 @@ static void enroll_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) { FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); - if (error) { fpi_device_enroll_complete (device, NULL, error); return; } if (self->resp_buf[MA_OVERHEAD] != 0x00) { - fpi_device_enroll_complete (device, NULL, - fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, - "StoreChar failed: 0x%02x", - self->resp_buf[MA_OVERHEAD])); + fpi_device_enroll_complete (device, NULL, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "StoreChar failed")); return; } - - /* Build an FpPrint encoding the FID */ FpPrint *print = NULL; fpi_device_get_enroll_data (device, &print); fpi_print_set_type (print, FPI_PRINT_RAW); @@ -600,15 +461,13 @@ static void ma_enroll (FpDevice *device) { FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); - self->enroll_stage = 0; - self->fid = -1; - self->waiting_for_lift = FALSE; + self->enroll_stage = 0; self->fid = -1; self->waiting_for_lift = FALSE; FpiSsm *ssm = fpi_ssm_new (device, enroll_run_state, ENROLL_NUM_STATES); fpi_ssm_start (ssm, enroll_ssm_done); } /* -------------------------------------------------------------------------- - * Verify state machine + * Verify state machine (Explicit single-finger validation) * -------------------------------------------------------------------------- */ enum { @@ -628,59 +487,47 @@ verify_run_state (FpiSsm *ssm, FpDevice *device) guint8 cmd[8]; switch (fpi_ssm_get_cur_state (ssm)) { - case VERIFY_GET_IMAGE: cmd[0] = MA_CMD_GET_IMAGE; ma_submit_cmd (ssm, device, cmd, 1); break; - case VERIFY_RECV_IMAGE: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - case VERIFY_GEN_CHAR: if (self->resp_buf[MA_OVERHEAD] != 0x00) { - fp_dbg ("GetImage not ready, retrying"); fpi_ssm_jump_to_state (ssm, VERIFY_GET_IMAGE); return; } fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); - cmd[0] = MA_CMD_GEN_CHAR; - cmd[1] = 0x01; /* use char buffer slot 1 for verification */ + cmd[0] = MA_CMD_GEN_CHAR; cmd[1] = 0x01; ma_submit_cmd (ssm, device, cmd, 2); break; - case VERIFY_RECV_GEN_CHAR: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - case VERIFY_SEARCH: { if (self->resp_buf[MA_OVERHEAD] != 0x00) { - fpi_ssm_mark_failed (ssm, - fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + fpi_ssm_mark_failed (ssm, fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); return; } - /* Get FID from enrolled print */ FpPrint *print = NULL; fpi_device_get_verify_data (device, &print); GVariant *data = NULL; g_object_get (print, "fpi-data", &data, NULL); gint fid = 0; g_variant_get (data, "(i)", &fid); + g_variant_unref (data); self->fid = fid; - /* CMD 0x66 fid_hi fid_lo — verify against specific FID */ cmd[0] = MA_CMD_SEARCH; - cmd[1] = (guint8)(self->fid >> 8); - cmd[2] = (guint8)(self->fid & 0xFF); + cmd[1] = (guint8)(self->fid >> 8); cmd[2] = (guint8)(self->fid & 0xFF); ma_submit_cmd (ssm, device, cmd, 3); break; } - case VERIFY_RECV_SEARCH: ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); break; - default: g_assert_not_reached (); } @@ -690,37 +537,248 @@ static void verify_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) { FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); - fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); if (error) { - /* Could be a retry error */ fpi_device_verify_complete (device, error); return; } + gboolean matched = (self->resp_buf[MA_OVERHEAD] == 0x00); + FpiMatchResult result = matched ? FPI_MATCH_SUCCESS : FPI_MATCH_FAIL; + FpPrint *print = NULL; - fpi_device_get_verify_data (device, &print); + if (matched) { + /* USE THE WORKING NATIVE GETTER! + * Since we know fpi_device_get_verify_data works flawlessly in your + * VERIFY_SEARCH block, we can just call it here to retrieve the template! + */ + fpi_device_get_verify_data (device, &print); + } + + /* Pass the actual print object instead of NULL so PAM/sudo know who matched */ + fpi_device_verify_report (device, result, print, NULL); + + fpi_device_verify_complete (device, NULL); +} - guint8 status = self->resp_buf[MA_OVERHEAD]; - if (status == 0x00) { - fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL); +/* -------------------------------------------------------------------------- + * Identify state machine (Device-wide global search for sudo) + * -------------------------------------------------------------------------- */ + +enum { + IDENTIFY_GET_IMAGE, + IDENTIFY_RECV_IMAGE, + IDENTIFY_GEN_CHAR, + IDENTIFY_RECV_GEN_CHAR, + IDENTIFY_SEARCH, + IDENTIFY_RECV_SEARCH, + IDENTIFY_CHECK_MATCH, + IDENTIFY_NUM_STATES, +}; + +static void +identify_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); + guint8 cmd[8]; + + switch (fpi_ssm_get_cur_state (ssm)) { + case IDENTIFY_GET_IMAGE: + cmd[0] = MA_CMD_GET_IMAGE; + ma_submit_cmd (ssm, device, cmd, 1); + break; + case IDENTIFY_RECV_IMAGE: + ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); + break; + case IDENTIFY_GEN_CHAR: + /* FIX: libfprint doesn't track previous states natively. + * Instead, check our loop tracker: if identify_index is 0, this is the + * initial run coming straight from a brand-new image capture. */ + if (self->identify_index == 0) { + if (self->resp_buf[MA_OVERHEAD] != 0x00) { + fp_dbg ("GetImage not ready, retrying"); + fpi_ssm_jump_to_state (ssm, IDENTIFY_GET_IMAGE); + return; + } + fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT); + } + + cmd[0] = MA_CMD_GEN_CHAR; cmd[1] = 0x01; + ma_submit_cmd (ssm, device, cmd, 2); + break; + cmd[0] = MA_CMD_GEN_CHAR; cmd[1] = 0x01; + ma_submit_cmd (ssm, device, cmd, 2); + break; + case IDENTIFY_RECV_GEN_CHAR: + ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); + break; + case IDENTIFY_SEARCH: { + if (self->resp_buf[MA_OVERHEAD] != 0x00) { + fpi_ssm_mark_failed (ssm, fpi_device_retry_new (FP_DEVICE_RETRY_GENERAL)); + return; + } + + GPtrArray *prints = NULL; + fpi_device_get_identify_data (device, &prints); + if (!prints || self->identify_index >= prints->len) { + fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "No templates to check")); + return; + } + + FpPrint *print = g_ptr_array_index (prints, self->identify_index); + GVariant *data = NULL; + g_object_get (print, "fpi-data", &data, NULL); + gint fid = 0; + if (data) { + /* NOTE: Check your enroll/verify functions. If they pack data as a plain + * integer instead of a tuple, change "(i)" to "i" here so fid doesn't default to 0. */ + g_variant_get (data, "(i)", &fid); + g_variant_unref (data); + } + self->fid = fid; + fp_dbg ("Searching hardware memory slot ID: %d (Index %d/%d)", self->fid, self->identify_index + 1, prints->len); + + cmd[0] = MA_CMD_SEARCH; + cmd[1] = (guint8)(self->fid >> 8); + cmd[2] = (guint8)(self->fid & 0xFF); + ma_submit_cmd (ssm, device, cmd, 3); + break; + } + case IDENTIFY_RECV_SEARCH: + ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); + break; + case IDENTIFY_CHECK_MATCH: { + guint8 status = self->resp_buf[MA_OVERHEAD]; + if (status == 0x00) { + fp_dbg ("Hardware match verified on slot ID %d!", self->fid); + fpi_ssm_mark_completed (ssm); + } else { + self->identify_index++; + GPtrArray *prints = NULL; + fpi_device_get_identify_data (device, &prints); + + /* FIX 2: Loop back to IDENTIFY_GEN_CHAR instead of IDENTIFY_SEARCH. + * This forces the hardware to re-extract features from its persistent image buffer + * to clear out the wiped character cache before testing the next slot ID. */ + if (prints && self->identify_index < prints->len) { + fp_dbg ("Slot %d match failed. Re-extracting characteristics for next index...", self->fid); + fpi_ssm_jump_to_state (ssm, IDENTIFY_GEN_CHAR); + } else { + fp_dbg ("Scan completed: No matching enrolled prints found."); + fpi_ssm_mark_completed (ssm); + } + } + break; + } + default: + g_assert_not_reached (); + } +} + +static void +identify_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); + fpi_device_report_finger_status (device, FP_FINGER_STATUS_NONE); + + if (error) { + fpi_device_identify_complete (device, error); + return; + } + + if (self->resp_buf[MA_OVERHEAD] == 0x00) { + GPtrArray *prints = NULL; + fpi_device_get_identify_data (device, &prints); + + if (prints && self->identify_index < prints->len) { + FpPrint *match = g_ptr_array_index (prints, self->identify_index); + fpi_device_identify_report (device, match, NULL, NULL); + } else { + fpi_device_identify_report (device, NULL, NULL, NULL); + } } else { - fp_dbg ("Search result: 0x%02x (no match)", status); - fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL); + fpi_device_identify_report (device, NULL, NULL, NULL); } - fpi_device_verify_complete (device, NULL); + fpi_device_identify_complete (device, NULL); } static void ma_verify (FpDevice *device) { - FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); - self->fid = -1; FpiSsm *ssm = fpi_ssm_new (device, verify_run_state, VERIFY_NUM_STATES); fpi_ssm_start (ssm, verify_ssm_done); } +static void +ma_identify (FpDevice *device) +{ + FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); + GPtrArray *prints = NULL; + + /* Fetch the array of templates passed down by the system */ + fpi_device_get_identify_data (device, &prints); + + /* FIX: If this is a fresh install or the user has no enrolled prints, + * short-circuit the hardware state machine entirely. Report a clean + * "No Match" so fprintd's de-duplication check passes successfully. */ + if (!prints || prints->len == 0) { + fp_dbg ("No templates enrolled on the system. Skipping hardware search loop."); + fpi_device_identify_report (device, NULL, NULL, NULL); + fpi_device_identify_complete (device, NULL); + return; + } + + /* Reset loop index and start the state machine for actual hardware matching */ + self->identify_index = 0; + FpiSsm *ssm = fpi_ssm_new (device, identify_run_state, IDENTIFY_NUM_STATES); + fpi_ssm_start (ssm, identify_ssm_done); +} + +/* -------------------------------------------------------------------------- + * Delete state machine + * -------------------------------------------------------------------------- */ + +enum { + DELETE_EMPTY, + DELETE_RECV_EMPTY, + DELETE_NUM_STATES, +}; + +static void +delete_run_state (FpiSsm *ssm, FpDevice *device) +{ + guint8 cmd[1]; + switch (fpi_ssm_get_cur_state (ssm)) { + case DELETE_EMPTY: + cmd[0] = MA_CMD_EMPTY; + ma_submit_cmd (ssm, device, cmd, 1); + break; + case DELETE_RECV_EMPTY: + ma_submit_recv (ssm, device, MA_OVERHEAD + 3 + 2); + break; + default: + g_assert_not_reached (); + } +} + +static void +delete_ssm_done (FpiSsm *ssm, FpDevice *device, GError *error) +{ + FpiDeviceMicroarray *self = FPI_DEVICE_MICROARRAY (device); + if (!error && self->resp_buf[MA_OVERHEAD] != 0x00) { + error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Delete Empty command failed"); + } + fpi_device_delete_complete (device, error); +} + +static void +ma_delete (FpDevice *device) +{ + FpiSsm *ssm = fpi_ssm_new (device, delete_run_state, DELETE_NUM_STATES); + fpi_ssm_start (ssm, delete_ssm_done); +} + /* -------------------------------------------------------------------------- * GObject boilerplate * -------------------------------------------------------------------------- */ @@ -762,10 +820,12 @@ fpi_device_microarray_class_init (FpiDeviceMicroarrayClass *klass) dev_class->nr_enroll_stages = MA_ENROLL_SAMPLES; dev_class->scan_type = FP_SCAN_TYPE_PRESS; - dev_class->open = ma_dev_open; - dev_class->close = ma_dev_close; - dev_class->enroll = ma_enroll; - dev_class->verify = ma_verify; + dev_class->open = ma_dev_open; + dev_class->close = ma_dev_close; + dev_class->enroll = ma_enroll; + dev_class->verify = ma_verify; + dev_class->identify = ma_identify; + dev_class->delete = ma_delete; fpi_device_class_auto_initialize_features (dev_class); -} +} \ No newline at end of file