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