libfprint/libfprint/drivers/validity/validity.c
Leonardo Francisco 8e0b1efbaf validity: Iteration 6 — Enrollment, Verification, and DB Management
Add core fingerprint operations: enrollment, verification, identification,
print listing, print deletion, and storage clearing.

New files:
- validity_db.h/c: On-chip template database operations — command builders
  for all DB commands (0x45-0x4B, 0x47-0x48, 0x51, 0x5E, 0x60, 0x62, 0x63,
  0x64, 0x68, 0x69, 0x6B), response parsers for DB info/user storage/user/
  record value/record children/new record ID, identity builder (UUID→VCSFW
  binary), finger data builder, and db_write_enable blob accessor.
- validity_enroll.c: 31-state enrollment SSM with interrupt-driven finger
  detection (EP 0x83), capture command orchestration via build_cmd_02(),
  enrollment session management (create/update/commit), DB record creation
  (user + finger), and LED glow feedback.
- validity_verify.c: 17-state verify/identify SSM with match command
  dispatching (cmd 0x5E for verify, cmd 0x60 for identify), 6-state list
  SSM for enumerating enrolled prints via GPtrArray, 8-state delete SSM,
  and clear_storage stub.

Modified files:
- validity.h: Added DB header include, 5 new state enums (CalibState,
  EnrollState, VerifyState, ListState, DeleteState), new struct fields
  for enrollment/verification/list/delete state, function declarations.
- validity.c: Replaced all operation stubs with real implementations,
  added cleanup for new fields in dev_close, wired all FpDevice methods.
- meson.build: Added 3 new source files to driver.
- tests/meson.build: Added test-validity-db executable.
- tests/validity/custom.py: Updated feature assertions (STORAGE,
  STORAGE_LIST, STORAGE_CLEAR now enabled).

Tests: 29 new unit tests in test-validity-db.c covering all command
builders, response parsers, identity/finger data builders, and blob
accessor. All 37 tests pass (0 fail, 2 skip).
2026-04-10 22:18:43 +00:00

801 lines
26 KiB
C

/*
* Main driver for Validity/Synaptics VCSFW fingerprint sensors
*
* Copyright (C) 2024 libfprint contributors
*
* 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
*/
#define FP_COMPONENT "validity"
#include "drivers_api.h"
#include "fpi-byte-reader.h"
#include "validity.h"
#include "validity_fwext.h"
#include "validity_tls.h"
#include "vcsfw_protocol.h"
G_DEFINE_TYPE (FpiDeviceValidity, fpi_device_validity, FP_TYPE_DEVICE)
static const FpIdEntry id_table[] = {
{ .vid = 0x138A, .pid = 0x0090, .driver_data = VALIDITY_DEV_90 },
{ .vid = 0x138A, .pid = 0x0097, .driver_data = VALIDITY_DEV_97 },
{ .vid = 0x06CB, .pid = 0x009A, .driver_data = VALIDITY_DEV_9A },
{ .vid = 0x138A, .pid = 0x009D, .driver_data = VALIDITY_DEV_9D },
{ .vid = 0, .pid = 0, .driver_data = 0 },
};
/* ================================================================
* Probe
* ================================================================
*
* Probe is done synchronously (like synaptics driver):
* 1) Open USB device
* 2) Send GET_VERSION (cmd 0x01)
* 3) Read response, parse firmware info
* 4) Close USB device
* 5) Report probe complete
*/
static void
dev_probe (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
GUsbDevice *usb_dev;
g_autoptr(FpiUsbTransfer) transfer = NULL;
GError *error = NULL;
FpiByteReader reader;
guint16 status;
g_autofree gchar *serial = NULL;
G_DEBUG_HERE ();
self->dev_type = fpi_device_get_driver_data (device);
usb_dev = fpi_device_get_usb_device (device);
if (!g_usb_device_open (usb_dev, &error))
{
fpi_device_probe_complete (device, NULL, NULL, error);
return;
}
if (!g_usb_device_reset (usb_dev, &error))
{
fp_dbg ("USB reset failed: %s", error->message);
goto err_close;
}
if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error))
goto err_close;
/* Send GET_VERSION (cmd 0x01) */
transfer = fpi_usb_transfer_new (device);
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT,
VALIDITY_USB_SEND_HEADER_LEN);
transfer->short_is_error = TRUE;
transfer->buffer[0] = VCSFW_CMD_GET_VERSION;
if (!fpi_usb_transfer_submit_sync (transfer, VALIDITY_USB_TIMEOUT, &error))
goto err_close;
/* Read response */
g_clear_pointer (&transfer, fpi_usb_transfer_unref);
transfer = fpi_usb_transfer_new (device);
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
VALIDITY_MAX_TRANSFER_LEN);
if (!fpi_usb_transfer_submit_sync (transfer, VALIDITY_USB_TIMEOUT, &error))
goto err_close;
/* Parse status */
fpi_byte_reader_init (&reader, transfer->buffer, transfer->actual_length);
if (!fpi_byte_reader_get_uint16_le (&reader, &status))
{
g_warning ("GET_VERSION response too short");
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
goto err_close;
}
if (status != VCSFW_STATUS_OK)
{
g_warning ("GET_VERSION returned error status: 0x%04x", status);
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
goto err_close;
}
/* Parse version info (data after the 2-byte status) */
if (!vcsfw_parse_version (transfer->buffer + 2,
transfer->actual_length - 2,
&self->version_info))
{
g_warning ("Failed to parse GET_VERSION response");
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
goto err_close;
}
fp_dbg ("Validity sensor firmware:");
fp_dbg (" Version: %d.%d",
self->version_info.version_major,
self->version_info.version_minor);
fp_dbg (" Product: %d", self->version_info.product);
fp_dbg (" Build Num: %d", self->version_info.build_num);
fp_dbg (" Build Time: %u", self->version_info.build_time);
/* Build a serial string from the serial number bytes */
serial = g_strdup_printf ("%02x%02x%02x%02x%02x%02x",
self->version_info.serial_number[0],
self->version_info.serial_number[1],
self->version_info.serial_number[2],
self->version_info.serial_number[3],
self->version_info.serial_number[4],
self->version_info.serial_number[5]);
g_usb_device_release_interface (usb_dev, 0, 0, NULL);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, serial, NULL, NULL);
return;
err_close:
g_usb_device_release_interface (usb_dev, 0, 0, NULL);
g_usb_device_close (usb_dev, NULL);
fpi_device_probe_complete (device, NULL, NULL, error);
}
/* ================================================================
* Open
* ================================================================
*
* Open claims the USB interface and sends the init sequence:
* 1) GET_VERSION (0x01)
* 2) UNKNOWN_INIT (0x19)
* 3) GET_FW_INFO (0x43 0x02) — check if fwext loaded
* 4) Upload firmware extension (if not loaded)
* 5) Send init_hardcoded blob
* 6) If no fwext: send init_hardcoded_clean_slate blob
*/
typedef enum {
OPEN_GET_VERSION = 0,
OPEN_RECV_VERSION,
OPEN_SEND_CMD19,
OPEN_RECV_CMD19,
OPEN_SEND_GET_FW_INFO,
OPEN_RECV_GET_FW_INFO,
OPEN_UPLOAD_FWEXT,
OPEN_TLS_READ_FLASH,
OPEN_TLS_DERIVE_PSK,
OPEN_TLS_HANDSHAKE,
OPEN_SENSOR_IDENTIFY,
OPEN_SENSOR_IDENTIFY_RECV,
OPEN_SENSOR_FACTORY_BITS,
OPEN_SENSOR_FACTORY_BITS_RECV,
OPEN_CAPTURE_SETUP,
OPEN_DONE,
OPEN_NUM_STATES,
} ValidityOpenSsmState;
/* Track whether fw extension is loaded */
typedef struct
{
gboolean fwext_loaded;
} OpenSsmData;
/* Callback for GET_FW_INFO response — check if fwext is loaded */
static void
fw_info_recv_cb (FpiUsbTransfer *transfer,
FpDevice *device,
gpointer user_data,
GError *error)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
if (error)
{
fpi_ssm_mark_failed (transfer->ssm, error);
return;
}
/* GET_FW_INFO response: first 2 bytes are status.
* 0x0000 = fwext loaded, 0xb004 = no fwext, others = error */
if (transfer->actual_length >= 2)
{
guint16 status = FP_READ_UINT16_LE (transfer->buffer);
if (status == VCSFW_STATUS_OK)
{
self->fwext_loaded = TRUE;
fp_info ("Firmware extension is loaded");
}
else
{
self->fwext_loaded = FALSE;
fp_info ("Firmware extension not loaded (status=0x%04x)", status);
}
}
else
{
self->fwext_loaded = FALSE;
fp_warn ("GET_FW_INFO response too short");
}
fpi_ssm_next_state (transfer->ssm);
}
/* Callback for optional flash-read child SSM */
static void
flash_read_ssm_done (FpiSsm *ssm,
FpDevice *dev,
GError *error)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (error)
{
fp_warn ("TLS flash read failed: %s — skipping TLS", error->message);
g_clear_error (&error);
fpi_ssm_jump_to_state (self->open_ssm, OPEN_DONE);
return;
}
fpi_ssm_next_state (self->open_ssm);
}
/* Callback for fwext upload child SSM */
static void
fwext_upload_ssm_done (FpiSsm *ssm,
FpDevice *dev,
GError *error)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (error)
{
fp_warn ("Firmware extension upload failed: %s", error->message);
/* Non-fatal: the device will work without fwext (TLS will be skipped)
* but user should be informed. Continue to OPEN_DONE. */
g_clear_error (&error);
fpi_ssm_jump_to_state (self->open_ssm, OPEN_DONE);
return;
}
/* After successful upload and reboot, the device will re-enumerate
* on USB. We cannot continue the open sequence — report an error
* that tells fprintd to retry. */
fp_info ("Firmware extension uploaded successfully — device rebooting");
fpi_ssm_mark_failed (self->open_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_REMOVED,
"Device rebooting after firmware upload"));
}
/* Callback for optional TLS handshake child SSM */
static void
tls_handshake_ssm_done (FpiSsm *ssm,
FpDevice *dev,
GError *error)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (error)
{
fp_warn ("TLS handshake failed: %s — continuing without TLS",
error->message);
g_clear_error (&error);
}
fpi_ssm_next_state (self->open_ssm);
}
static void
open_run_state (FpiSsm *ssm,
FpDevice *dev)
{
FpiUsbTransfer *transfer;
switch (fpi_ssm_get_cur_state (ssm))
{
case OPEN_GET_VERSION:
transfer = fpi_usb_transfer_new (dev);
transfer->short_is_error = TRUE;
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT,
VALIDITY_USB_SEND_HEADER_LEN);
transfer->buffer[0] = VCSFW_CMD_GET_VERSION;
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_RECV_VERSION:
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
VALIDITY_MAX_TRANSFER_LEN);
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_SEND_CMD19:
transfer = fpi_usb_transfer_new (dev);
transfer->short_is_error = TRUE;
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT,
VALIDITY_USB_SEND_HEADER_LEN);
transfer->buffer[0] = VCSFW_CMD_UNKNOWN_INIT;
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_RECV_CMD19:
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
VALIDITY_MAX_TRANSFER_LEN);
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
break;
case OPEN_SEND_GET_FW_INFO:
{
guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, 0x02 };
transfer = fpi_usb_transfer_new (dev);
transfer->short_is_error = TRUE;
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT,
sizeof (cmd));
memcpy (transfer->buffer, cmd, sizeof (cmd));
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fpi_ssm_usb_transfer_cb, NULL);
}
break;
case OPEN_RECV_GET_FW_INFO:
transfer = fpi_usb_transfer_new (dev);
transfer->ssm = ssm;
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
VALIDITY_MAX_TRANSFER_LEN);
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
fw_info_recv_cb, NULL);
break;
case OPEN_UPLOAD_FWEXT:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* If fwext is already loaded, skip upload */
if (self->fwext_loaded)
{
fpi_ssm_next_state (ssm);
return;
}
/* In emulation mode, skip upload — no real device */
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
{
fp_dbg ("Emulation mode — skipping fwext upload");
fpi_ssm_next_state (ssm);
return;
}
fp_info ("Firmware extension not loaded — starting upload");
self->open_ssm = ssm;
FpiSsm *fwext_ssm = validity_fwext_upload_ssm_new (dev);
fpi_ssm_start (fwext_ssm, fwext_upload_ssm_done);
}
break;
case OPEN_TLS_READ_FLASH:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* In emulation mode (tests), skip TLS — no real device to talk to */
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
{
fp_dbg ("Emulation mode — skipping TLS flash read");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
/* Without fwext, flash partition isn't accessible */
if (!self->fwext_loaded)
{
fp_info ("No firmware extension — skipping TLS "
"(device needs pairing or fwext upload)");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
/* Read flash partition 1 to get TLS keys.
* Uses standalone SSM (not subsm) so failure is non-fatal. */
self->open_ssm = ssm;
FpiSsm *flash_ssm = fpi_ssm_new (dev,
validity_tls_flash_read_run_state,
TLS_FLASH_READ_NUM_STATES);
fpi_ssm_start (flash_ssm, flash_read_ssm_done);
}
break;
case OPEN_TLS_DERIVE_PSK:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* Derive PSK from hardware identity (DMI) */
validity_tls_derive_psk (&self->tls);
/* Flash response format (after 2-byte status already stripped):
* [size:4 LE][unknown:2][flash_data:size]
* See python-validity: sz, = unpack('<xxLxx', rsp[:8]) */
GError *error = NULL;
if (self->cmd_response_data && self->cmd_response_len > 6)
{
guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data);
const guint8 *flash_data = self->cmd_response_data + 6;
gsize flash_avail = self->cmd_response_len - 6;
if (flash_sz > flash_avail)
flash_sz = flash_avail;
fp_dbg ("TLS flash: %u bytes of data (response had %zu)",
flash_sz, self->cmd_response_len);
if (!validity_tls_parse_flash (&self->tls,
flash_data,
flash_sz,
&error))
{
fp_warn ("TLS flash parse failed: %s — "
"device may need pairing", error->message);
/* Non-fatal for now: skip TLS handshake */
g_clear_error (&error);
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
}
else
{
fp_warn ("No flash data available — skipping TLS");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
fpi_ssm_next_state (ssm);
}
break;
case OPEN_TLS_HANDSHAKE:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (!self->tls.keys_loaded)
{
fp_info ("TLS keys not loaded — skipping handshake");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
self->open_ssm = ssm;
FpiSsm *tls_ssm = fpi_ssm_new (dev,
validity_tls_handshake_run_state,
TLS_HS_NUM_STATES);
fpi_ssm_start (tls_ssm, tls_handshake_ssm_done);
}
break;
case OPEN_SENSOR_IDENTIFY:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* Without TLS, sensor identification is not possible */
if (!self->tls.secure_rx)
{
fp_info ("No TLS session — skipping sensor identification");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
/* Send cmd 0x75 (identify_sensor) via TLS.
* NULL callback: subsm auto-advances, response stashed in
* self->cmd_response_data for the RECV state. */
guint8 cmd[] = { VCSFW_CMD_IDENTIFY_SENSOR };
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
}
break;
case OPEN_SENSOR_IDENTIFY_RECV:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (self->cmd_response_status != VCSFW_STATUS_OK)
{
fp_warn ("identify_sensor failed: status=0x%04x",
self->cmd_response_status);
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
if (!validity_sensor_parse_identify (self->cmd_response_data,
self->cmd_response_len,
&self->sensor.ident))
{
fp_warn ("identify_sensor: response too short");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
fp_info ("Sensor hardware: major=0x%04x version=0x%04x",
self->sensor.ident.hw_major,
self->sensor.ident.hw_version);
/* Look up device info and sensor type */
self->sensor.device_info = validity_device_info_lookup (
self->sensor.ident.hw_major,
self->sensor.ident.hw_version);
if (self->sensor.device_info)
{
fp_info ("Device: %s (type=0x%04x)",
self->sensor.device_info->name,
self->sensor.device_info->type);
self->sensor.type_info = validity_sensor_type_info_lookup (
self->sensor.device_info->type);
if (self->sensor.type_info)
fp_info ("Sensor type: 0x%04x, %u bytes/line, %ux repeat",
self->sensor.type_info->sensor_type,
self->sensor.type_info->bytes_per_line,
self->sensor.type_info->repeat_multiplier);
else
fp_warn ("Unknown sensor type 0x%04x",
self->sensor.device_info->type);
}
else
{
fp_warn ("Unknown hardware major=0x%04x version=0x%04x",
self->sensor.ident.hw_major,
self->sensor.ident.hw_version);
}
fpi_ssm_next_state (ssm);
}
break;
case OPEN_SENSOR_FACTORY_BITS:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* Factory bits are needed for calibration. If sensor wasn't
* identified, skip this step. */
if (!self->sensor.device_info)
{
fp_info ("No sensor info — skipping factory bits");
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
return;
}
/* Build and send cmd 0x6f (GET_FACTORY_BITS) with tag 0x0e00 */
guint8 cmd[9];
validity_sensor_build_factory_bits_cmd (0x0e00, cmd, sizeof (cmd));
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
}
break;
case OPEN_SENSOR_FACTORY_BITS_RECV:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
if (self->cmd_response_status != VCSFW_STATUS_OK)
{
fp_warn ("get_factory_bits failed: status=0x%04x",
self->cmd_response_status);
/* Non-fatal: calibration will have to work without factory data */
fpi_ssm_next_state (ssm);
return;
}
/* Store raw factory bits for calibration (iter 5) */
g_clear_pointer (&self->sensor.factory_bits, g_free);
if (self->cmd_response_data && self->cmd_response_len > 0)
{
self->sensor.factory_bits = g_memdup2 (self->cmd_response_data,
self->cmd_response_len);
self->sensor.factory_bits_len = self->cmd_response_len;
fp_info ("Factory bits: %zu bytes", self->sensor.factory_bits_len);
}
fpi_ssm_next_state (ssm);
}
break;
case OPEN_CAPTURE_SETUP:
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
/* Initialize capture state from sensor identification and factory bits.
* Requires: sensor.type_info and sensor.device_info from IDENTIFY,
* sensor.factory_bits from FACTORY_BITS. */
if (!self->sensor.type_info || !self->sensor.device_info)
{
fp_info ("No sensor type info — skipping capture setup");
fpi_ssm_next_state (ssm);
return;
}
validity_capture_state_init (&self->capture);
if (!validity_capture_state_setup (&self->capture,
self->sensor.type_info,
self->sensor.device_info->type,
self->version_info.version_major,
self->version_info.version_minor,
self->sensor.factory_bits,
self->sensor.factory_bits_len))
{
fp_warn ("Capture state setup failed — "
"enrollment/verification will not be available");
/* Non-fatal: device can still be used for identification
* if calibration data exists on flash */
}
else
{
fp_info ("Capture state: %u bytes/line, %u lines/frame, "
"type1=%d",
self->capture.bytes_per_line,
self->capture.lines_per_frame,
self->capture.is_type1_device);
}
fpi_ssm_next_state (ssm);
}
break;
case OPEN_DONE:
/* All init commands sent. Mark open complete. */
fpi_ssm_mark_completed (ssm);
break;
}
}
static void
open_ssm_done (FpiSsm *ssm,
FpDevice *dev,
GError *error)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
self->cmd_ssm = NULL;
if (error)
{
g_usb_device_release_interface (fpi_device_get_usb_device (dev), 0, 0, NULL);
fpi_device_open_complete (dev, error);
return;
}
fp_info ("Validity sensor opened successfully");
fpi_device_open_complete (dev, NULL);
}
static void
dev_open (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
GError *error = NULL;
FpiSsm *ssm;
G_DEBUG_HERE ();
self->interrupt_cancellable = g_cancellable_new ();
validity_tls_init (&self->tls);
validity_sensor_state_init (&self->sensor);
validity_capture_state_init (&self->capture);
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
{
fpi_device_open_complete (device, error);
return;
}
ssm = fpi_ssm_new (device, open_run_state, OPEN_NUM_STATES);
self->cmd_ssm = ssm;
fpi_ssm_start (ssm, open_ssm_done);
}
/* ================================================================
* Close
* ================================================================ */
static void
dev_close (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
g_autoptr(GError) error = NULL;
G_DEBUG_HERE ();
g_clear_pointer (&self->cmd_response_data, g_free);
self->cmd_response_len = 0;
g_clear_pointer (&self->enroll_template, g_free);
self->enroll_template_len = 0;
g_clear_pointer (&self->bulk_data, g_free);
self->bulk_data_len = 0;
validity_user_storage_clear (&self->list_storage);
validity_capture_state_clear (&self->capture);
validity_sensor_state_clear (&self->sensor);
validity_tls_free (&self->tls);
g_clear_object (&self->interrupt_cancellable);
g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error);
fpi_device_close_complete (device, g_steal_pointer (&error));
}
/* ================================================================
* Enroll / Verify / Identify / Delete / List
* ================================================================
*
* Real implementations now in validity_enroll.c and validity_verify.c.
* These thin wrappers call the external SSM starters.
*/
static void
cancel (FpDevice *device)
{
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
g_cancellable_cancel (self->interrupt_cancellable);
g_clear_object (&self->interrupt_cancellable);
self->interrupt_cancellable = g_cancellable_new ();
}
/* ================================================================
* GObject boilerplate
* ================================================================ */
static void
fpi_device_validity_init (FpiDeviceValidity *self)
{
}
static void
fpi_device_validity_class_init (FpiDeviceValidityClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
dev_class->id = FP_COMPONENT;
dev_class->full_name = "Validity VCSFW Fingerprint Sensor";
dev_class->type = FP_DEVICE_TYPE_USB;
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
dev_class->id_table = id_table;
dev_class->nr_enroll_stages = VALIDITY_ENROLL_STAGES;
dev_class->temp_hot_seconds = -1;
dev_class->probe = dev_probe;
dev_class->open = dev_open;
dev_class->close = dev_close;
dev_class->enroll = validity_enroll;
dev_class->verify = validity_verify;
dev_class->identify = validity_identify;
dev_class->delete = validity_delete;
dev_class->list = validity_list;
dev_class->clear_storage = validity_clear_storage;
dev_class->cancel = cancel;
fpi_device_class_auto_initialize_features (dev_class);
}