mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-18 13:08:26 +02:00
Comprehensive bugfix for issues found during code audit: 1. parse_match_result (CRITICAL): Replace dead while loop + hardcoded offsets with proper TLV dict parsing (tag LE16 + len LE16 + data) matching python-validity's parse_dict(). Extracts user_dbid (tag 1), subtype (tag 3), and hash (tag 4) from match result. 2. ENROLL_CREATE_USER (CRITICAL): Fix NULL user_id crash — g_uuid_string_random() now generates UUID for user identity instead of passing NULL to g_variant_new_string(). 3. Identify gallery matching: Match sensor result against gallery by comparing finger subtype instead of always returning first entry. 4. Field abuse: Add dedicated enroll_user_dbid field to FpiDeviceValidity instead of reusing delete_storage_dbid for enrollment state. 5. Delete SSM: Full implementation — enumerate users via get_user_storage, iterate users to find matching finger subtype, delete via cmd 0x48 (del_record). Proper error handling for missing records. 6. match_finger double allocation: Remove redundant 12-byte alloc/free, single clean 13-byte allocation per python-validity format. 7. clear_storage: Full SSM implementation — enumerate user storage, del_record for each user. Replaces NOT_SUPPORTED stub. 8. Clean stale TODO/placeholder comments. All 37 tests pass (0 fail, 2 skip — unchanged baseline).
994 lines
29 KiB
C
994 lines
29 KiB
C
/*
|
|
* Verify and Identify state machines for Validity/Synaptics VCSFW sensors
|
|
*
|
|
* Implements FpDevice::verify and FpDevice::identify virtual methods.
|
|
*
|
|
* Both operations share the same state machine since the sensor-side
|
|
* flow is nearly identical:
|
|
* 1. LED on
|
|
* 2. Send capture command (IDENTIFY mode)
|
|
* 3. Wait for finger-down interrupt (EP 0x83)
|
|
* 4. Wait for scan-complete interrupt
|
|
* 5. Start match (cmd 0x5E)
|
|
* 6. Wait for match interrupt
|
|
* 7. Get match result (cmd 0x60)
|
|
* 8. Match cleanup (cmd 0x62)
|
|
* 9. LED off
|
|
* 10. Report result
|
|
*
|
|
* The difference between verify and identify is only in how the
|
|
* result is reported to libfprint.
|
|
*
|
|
* Also implements list and delete operations.
|
|
*
|
|
* Reference: python-validity sensor.py Sensor.identify(), Sensor.match_finger()
|
|
*
|
|
* 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 "fpi-byte-utils.h"
|
|
#include "fpi-print.h"
|
|
#include "validity.h"
|
|
#include "vcsfw_protocol.h"
|
|
|
|
/* ================================================================
|
|
* Interrupt helpers (shared with enrollment)
|
|
* ================================================================ */
|
|
|
|
static void
|
|
verify_interrupt_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *device,
|
|
gpointer user_data,
|
|
GError *error)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
|
FpiSsm *ssm = user_data;
|
|
guint8 int_type;
|
|
|
|
if (error)
|
|
{
|
|
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
|
{
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_REMOVED));
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
fpi_ssm_mark_failed (ssm, error);
|
|
return;
|
|
}
|
|
|
|
if (transfer->actual_length < 1)
|
|
{
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
return;
|
|
}
|
|
|
|
int_type = transfer->buffer[0];
|
|
|
|
fp_dbg ("Verify interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
|
int_type, transfer->actual_length);
|
|
|
|
/* During match wait, type 3 = result available */
|
|
if (fpi_ssm_get_cur_state (ssm) == VERIFY_WAIT_MATCH_INT)
|
|
{
|
|
if (int_type == 3)
|
|
{
|
|
fpi_ssm_next_state (ssm);
|
|
return;
|
|
}
|
|
/* Not ready yet, keep waiting */
|
|
goto read_again;
|
|
}
|
|
|
|
/* During finger wait: type 2 = finger down */
|
|
if (fpi_ssm_get_cur_state (ssm) == VERIFY_WAIT_FINGER)
|
|
{
|
|
if (int_type == VALIDITY_INT_FINGER_DOWN)
|
|
{
|
|
fpi_ssm_next_state (ssm);
|
|
return;
|
|
}
|
|
/* type 0 = capture started, expected */
|
|
if (int_type == 0)
|
|
goto read_again;
|
|
}
|
|
|
|
/* During scan wait: type 3 with scan_complete bit */
|
|
if (fpi_ssm_get_cur_state (ssm) == VERIFY_WAIT_SCAN_COMPLETE)
|
|
{
|
|
if (int_type == 3 && transfer->actual_length >= 3 &&
|
|
(transfer->buffer[2] & VALIDITY_INT_SCAN_COMPLETE))
|
|
{
|
|
fpi_ssm_next_state (ssm);
|
|
return;
|
|
}
|
|
if (int_type == 3 || int_type == 0)
|
|
goto read_again;
|
|
}
|
|
|
|
/* Unexpected, but keep listening */
|
|
fp_dbg ("Ignoring verify interrupt type 0x%02x in state %d",
|
|
int_type, fpi_ssm_get_cur_state (ssm));
|
|
|
|
read_again:
|
|
{
|
|
FpiUsbTransfer *new_transfer = fpi_usb_transfer_new (device);
|
|
fpi_usb_transfer_fill_interrupt (new_transfer, VALIDITY_EP_INT_IN,
|
|
VALIDITY_USB_INT_DATA_SIZE);
|
|
fpi_usb_transfer_submit (new_transfer, VALIDITY_USB_TIMEOUT,
|
|
self->interrupt_cancellable,
|
|
verify_interrupt_cb, ssm);
|
|
}
|
|
}
|
|
|
|
static void
|
|
verify_start_interrupt_wait (FpiDeviceValidity *self,
|
|
FpiSsm *ssm)
|
|
{
|
|
FpiUsbTransfer *transfer;
|
|
|
|
transfer = fpi_usb_transfer_new (FP_DEVICE (self));
|
|
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
|
VALIDITY_USB_INT_DATA_SIZE);
|
|
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT,
|
|
self->interrupt_cancellable,
|
|
verify_interrupt_cb, ssm);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Match result parsing
|
|
*
|
|
* cmd 0x60 response: len(2LE) | dict_data[len]
|
|
* dict_data is a TLV dictionary (python-validity parse_dict()):
|
|
* while data: tag(2LE) | len(2LE) | value[len]
|
|
* In the match response:
|
|
* tag 1 → user_dbid(4LE)
|
|
* tag 3 → subtype(2LE)
|
|
* tag 4 → hash (variable length)
|
|
* ================================================================ */
|
|
|
|
typedef struct
|
|
{
|
|
gboolean matched;
|
|
guint32 user_dbid;
|
|
guint16 subtype;
|
|
guint8 *hash;
|
|
gsize hash_len;
|
|
} MatchResult;
|
|
|
|
static void
|
|
match_result_clear (MatchResult *r)
|
|
{
|
|
g_clear_pointer (&r->hash, g_free);
|
|
memset (r, 0, sizeof (*r));
|
|
}
|
|
|
|
/**
|
|
* parse_match_result:
|
|
* @data: response payload from cmd 0x60 (after status word)
|
|
* @data_len: length of @data
|
|
* @result: (out): parsed match result
|
|
*
|
|
* Parses the TLV dictionary from a get_match_result (cmd 0x60) response.
|
|
*
|
|
* python-validity reference (sensor.py match_finger()):
|
|
* rsp = self.parse_dict(rsp)
|
|
* usrid, subtype, hsh = rsp[1], rsp[3], rsp[4]
|
|
*
|
|
* where parse_dict() is:
|
|
* while len(x) > 0:
|
|
* (t, l), x = unpack('<HH', x[:4]), x[4:]
|
|
* rc[t], x = x[:l], x[l:]
|
|
*
|
|
* Returns: %TRUE if parsing succeeded (result may still be !matched
|
|
* if the dict was empty), %FALSE on malformed data.
|
|
*/
|
|
static gboolean
|
|
parse_match_result (const guint8 *data,
|
|
gsize data_len,
|
|
MatchResult *result)
|
|
{
|
|
FpiByteReader reader;
|
|
guint16 total_len;
|
|
|
|
memset (result, 0, sizeof (*result));
|
|
|
|
if (data_len < 2)
|
|
return FALSE;
|
|
|
|
fpi_byte_reader_init (&reader, data, data_len);
|
|
|
|
if (!fpi_byte_reader_get_uint16_le (&reader, &total_len))
|
|
return FALSE;
|
|
|
|
gsize remaining = MIN (total_len, data_len - 2);
|
|
const guint8 *dict_data;
|
|
|
|
if (!fpi_byte_reader_get_data (&reader, remaining, &dict_data))
|
|
return FALSE;
|
|
|
|
/* Parse TLV entries: tag(2LE) | len(2LE) | value[len] */
|
|
FpiByteReader dict_reader;
|
|
fpi_byte_reader_init (&dict_reader, dict_data, remaining);
|
|
|
|
while (fpi_byte_reader_get_remaining (&dict_reader) >= 4)
|
|
{
|
|
guint16 tag, entry_len;
|
|
const guint8 *entry_data;
|
|
|
|
if (!fpi_byte_reader_get_uint16_le (&dict_reader, &tag))
|
|
break;
|
|
if (!fpi_byte_reader_get_uint16_le (&dict_reader, &entry_len))
|
|
break;
|
|
if (!fpi_byte_reader_get_data (&dict_reader, entry_len, &entry_data))
|
|
break;
|
|
|
|
switch (tag)
|
|
{
|
|
case 1: /* user_dbid (4 bytes LE) */
|
|
if (entry_len >= 4)
|
|
{
|
|
result->user_dbid = FP_READ_UINT32_LE (entry_data);
|
|
result->matched = TRUE;
|
|
}
|
|
break;
|
|
|
|
case 3: /* subtype (2 bytes LE) */
|
|
if (entry_len >= 2)
|
|
result->subtype = FP_READ_UINT16_LE (entry_data);
|
|
break;
|
|
|
|
case 4: /* hash (variable) */
|
|
if (entry_len > 0)
|
|
{
|
|
result->hash = g_memdup2 (entry_data, entry_len);
|
|
result->hash_len = entry_len;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fp_dbg ("parse_match_result: ignoring unknown tag %u (len=%u)",
|
|
tag, entry_len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* ================================================================
|
|
* Verify/Identify SSM
|
|
* ================================================================ */
|
|
|
|
static void
|
|
verify_run_state (FpiSsm *ssm,
|
|
FpDevice *dev)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case VERIFY_LED_ON:
|
|
{
|
|
gsize cmd_len;
|
|
const guint8 *cmd = validity_capture_glow_start_cmd (&cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_LED_ON_RECV:
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case VERIFY_BUILD_CAPTURE:
|
|
{
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_capture_build_cmd_02 (&self->capture,
|
|
self->sensor.type_info,
|
|
VALIDITY_CAPTURE_IDENTIFY,
|
|
&cmd_len);
|
|
if (!cmd)
|
|
{
|
|
fp_warn ("Failed to build identify capture command");
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
|
|
return;
|
|
}
|
|
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_CAPTURE_SEND:
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case VERIFY_CAPTURE_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fp_warn ("Capture (identify) failed: status=0x%04x",
|
|
self->cmd_response_status);
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
return;
|
|
}
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_WAIT_FINGER:
|
|
verify_start_interrupt_wait (self, ssm);
|
|
break;
|
|
|
|
case VERIFY_WAIT_SCAN_COMPLETE:
|
|
verify_start_interrupt_wait (self, ssm);
|
|
break;
|
|
|
|
case VERIFY_MATCH_START:
|
|
{
|
|
/* cmd 0x5E: match_finger */
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_match_finger (&cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_MATCH_START_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fp_warn ("match_finger failed: status=0x%04x",
|
|
self->cmd_response_status);
|
|
/* No match — continue to cleanup */
|
|
fpi_ssm_jump_to_state (ssm, VERIFY_CLEANUP);
|
|
return;
|
|
}
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_WAIT_MATCH_INT:
|
|
/* Wait for interrupt type 3 indicating match result ready */
|
|
verify_start_interrupt_wait (self, ssm);
|
|
break;
|
|
|
|
case VERIFY_GET_RESULT:
|
|
{
|
|
/* cmd 0x60: get_match_result */
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_match_result (&cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_GET_RESULT_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fp_info ("No match found (status=0x%04x)",
|
|
self->cmd_response_status);
|
|
/* Store no-match indicator */
|
|
g_clear_pointer (&self->bulk_data, g_free);
|
|
self->bulk_data_len = 0;
|
|
}
|
|
else if (self->cmd_response_data && self->cmd_response_len > 0)
|
|
{
|
|
/* Store match result for later reporting */
|
|
g_clear_pointer (&self->bulk_data, g_free);
|
|
self->bulk_data = g_memdup2 (self->cmd_response_data,
|
|
self->cmd_response_len);
|
|
self->bulk_data_len = self->cmd_response_len;
|
|
}
|
|
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_CLEANUP:
|
|
{
|
|
/* cmd 0x62: match_cleanup */
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_match_cleanup (&cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_CLEANUP_RECV:
|
|
/* Cleanup status doesn't matter */
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case VERIFY_LED_OFF:
|
|
{
|
|
gsize cmd_len;
|
|
const guint8 *cmd = validity_capture_glow_end_cmd (&cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
}
|
|
break;
|
|
|
|
case VERIFY_LED_OFF_RECV:
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case VERIFY_DONE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
verify_ssm_done (FpiSsm *ssm,
|
|
FpDevice *dev,
|
|
GError *error)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
if (error)
|
|
{
|
|
if (self->identify_mode)
|
|
fpi_device_identify_complete (dev, error);
|
|
else
|
|
fpi_device_verify_complete (dev, error);
|
|
|
|
g_clear_pointer (&self->bulk_data, g_free);
|
|
self->bulk_data_len = 0;
|
|
return;
|
|
}
|
|
|
|
/* Parse stored match result */
|
|
MatchResult match = { 0 };
|
|
gboolean have_match = FALSE;
|
|
|
|
if (self->bulk_data && self->bulk_data_len > 0)
|
|
{
|
|
if (parse_match_result (self->bulk_data, self->bulk_data_len, &match))
|
|
have_match = match.matched;
|
|
}
|
|
|
|
if (self->identify_mode)
|
|
{
|
|
if (have_match)
|
|
{
|
|
fp_info ("Identify matched: user_dbid=%u subtype=%u",
|
|
match.user_dbid, match.subtype);
|
|
|
|
/* Match the sensor result against the gallery by comparing
|
|
* the finger subtype. The sensor does the actual 1:N match
|
|
* internally; we just need to find which gallery FpPrint
|
|
* corresponds to the matched subtype. */
|
|
FpPrint *gallery_match = NULL;
|
|
GPtrArray *gallery = NULL;
|
|
|
|
fpi_device_get_identify_data (dev, &gallery);
|
|
|
|
if (gallery)
|
|
{
|
|
gint matched_finger = validity_subtype_to_finger (match.subtype);
|
|
|
|
for (guint i = 0; i < gallery->len; i++)
|
|
{
|
|
FpPrint *candidate = g_ptr_array_index (gallery, i);
|
|
if (fp_print_get_finger (candidate) == (FpFinger) matched_finger)
|
|
{
|
|
gallery_match = candidate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If no finger match, fall back to first gallery print —
|
|
* the sensor confirmed a match even if we can't correlate
|
|
* the subtype to a specific gallery entry. */
|
|
if (!gallery_match && gallery->len > 0)
|
|
gallery_match = g_ptr_array_index (gallery, 0);
|
|
}
|
|
|
|
fpi_device_identify_report (dev, gallery_match, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
fpi_device_identify_report (dev, NULL, NULL, NULL);
|
|
}
|
|
|
|
fpi_device_identify_complete (dev, NULL);
|
|
}
|
|
else
|
|
{
|
|
/* Verify mode */
|
|
if (have_match)
|
|
{
|
|
fp_info ("Verify matched: user_dbid=%u", match.user_dbid);
|
|
fpi_device_verify_report (dev, FPI_MATCH_SUCCESS, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
fp_info ("Verify: no match");
|
|
fpi_device_verify_report (dev, FPI_MATCH_FAIL, NULL, NULL);
|
|
}
|
|
|
|
fpi_device_verify_complete (dev, NULL);
|
|
}
|
|
|
|
match_result_clear (&match);
|
|
g_clear_pointer (&self->bulk_data, g_free);
|
|
self->bulk_data_len = 0;
|
|
}
|
|
|
|
void
|
|
validity_verify (FpDevice *device)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
|
FpiSsm *ssm;
|
|
|
|
G_DEBUG_HERE ();
|
|
|
|
self->identify_mode = FALSE;
|
|
|
|
ssm = fpi_ssm_new (device, verify_run_state, VERIFY_NUM_STATES);
|
|
fpi_ssm_start (ssm, verify_ssm_done);
|
|
}
|
|
|
|
void
|
|
validity_identify (FpDevice *device)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
|
FpiSsm *ssm;
|
|
|
|
G_DEBUG_HERE ();
|
|
|
|
self->identify_mode = TRUE;
|
|
|
|
ssm = fpi_ssm_new (device, verify_run_state, VERIFY_NUM_STATES);
|
|
fpi_ssm_start (ssm, verify_ssm_done);
|
|
}
|
|
|
|
/* ================================================================
|
|
* List prints — enumerate enrolled fingerprints from sensor DB
|
|
* ================================================================ */
|
|
|
|
static void
|
|
list_run_state (FpiSsm *ssm,
|
|
FpDevice *dev)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
GPtrArray *prints_array = fpi_ssm_get_data (ssm);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case LIST_GET_STORAGE:
|
|
{
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_user_storage (
|
|
VALIDITY_STORAGE_NAME, &cmd_len);
|
|
self->list_user_idx = 0;
|
|
memset (&self->list_storage, 0, sizeof (self->list_storage));
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case LIST_GET_STORAGE_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fp_info ("No user storage found (status=0x%04x)",
|
|
self->cmd_response_status);
|
|
fpi_ssm_jump_to_state (ssm, LIST_DONE);
|
|
return;
|
|
}
|
|
|
|
if (!self->cmd_response_data ||
|
|
!validity_db_parse_user_storage (self->cmd_response_data,
|
|
self->cmd_response_len,
|
|
&self->list_storage))
|
|
{
|
|
fp_info ("Failed to parse user storage — no enrolled prints");
|
|
fpi_ssm_jump_to_state (ssm, LIST_DONE);
|
|
return;
|
|
}
|
|
|
|
fp_info ("Storage '%s': %u users",
|
|
self->list_storage.name ? self->list_storage.name : "",
|
|
self->list_storage.user_count);
|
|
|
|
if (self->list_storage.user_count == 0)
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, LIST_DONE);
|
|
return;
|
|
}
|
|
|
|
self->list_user_idx = 0;
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case LIST_GET_USER:
|
|
{
|
|
if (self->list_user_idx >= self->list_storage.user_count)
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, LIST_DONE);
|
|
return;
|
|
}
|
|
|
|
guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx];
|
|
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case LIST_GET_USER_RECV:
|
|
{
|
|
if (self->cmd_response_status == VCSFW_STATUS_OK &&
|
|
self->cmd_response_data)
|
|
{
|
|
ValidityUser user = { 0 };
|
|
|
|
if (validity_db_parse_user (self->cmd_response_data,
|
|
self->cmd_response_len,
|
|
&user))
|
|
{
|
|
for (guint16 i = 0; i < user.finger_count; i++)
|
|
{
|
|
FpPrint *print = fp_print_new (dev);
|
|
gint finger = validity_subtype_to_finger (
|
|
user.fingers[i].subtype);
|
|
|
|
fpi_print_set_type (print, FPI_PRINT_RAW);
|
|
fpi_print_set_device_stored (print, TRUE);
|
|
if (finger >= 0)
|
|
fp_print_set_finger (print, (FpFinger) finger);
|
|
|
|
g_ptr_array_add (prints_array, print);
|
|
}
|
|
|
|
validity_user_clear (&user);
|
|
}
|
|
}
|
|
|
|
self->list_user_idx++;
|
|
|
|
if (self->list_user_idx < self->list_storage.user_count)
|
|
fpi_ssm_jump_to_state (ssm, LIST_GET_USER);
|
|
else
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case LIST_DONE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
list_ssm_done (FpiSsm *ssm,
|
|
FpDevice *dev,
|
|
GError *error)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
GPtrArray *prints_array = fpi_ssm_get_data (ssm);
|
|
|
|
validity_user_storage_clear (&self->list_storage);
|
|
|
|
if (error)
|
|
{
|
|
fpi_device_list_complete (dev, NULL, error);
|
|
return;
|
|
}
|
|
|
|
fpi_device_list_complete (dev, g_steal_pointer (&prints_array), NULL);
|
|
}
|
|
|
|
void
|
|
validity_list (FpDevice *device)
|
|
{
|
|
FpiSsm *ssm;
|
|
GPtrArray *prints_array;
|
|
|
|
G_DEBUG_HERE ();
|
|
|
|
prints_array = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
ssm = fpi_ssm_new (device, list_run_state, LIST_NUM_STATES);
|
|
fpi_ssm_set_data (ssm, prints_array, (GDestroyNotify) g_ptr_array_unref);
|
|
fpi_ssm_start (ssm, list_ssm_done);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Delete print — remove a fingerprint record from the sensor DB
|
|
* ================================================================ */
|
|
|
|
static void
|
|
delete_run_state (FpiSsm *ssm,
|
|
FpDevice *dev)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case DELETE_GET_STORAGE:
|
|
{
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_user_storage (
|
|
VALIDITY_STORAGE_NAME, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case DELETE_GET_STORAGE_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
|
return;
|
|
}
|
|
|
|
/* Parse into list_storage (shared with list SSM, not concurrent) */
|
|
validity_user_storage_clear (&self->list_storage);
|
|
if (!self->cmd_response_data ||
|
|
!validity_db_parse_user_storage (self->cmd_response_data,
|
|
self->cmd_response_len,
|
|
&self->list_storage))
|
|
{
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
|
return;
|
|
}
|
|
|
|
self->delete_storage_dbid = self->list_storage.dbid;
|
|
|
|
/* Extract finger subtype from the print to delete */
|
|
{
|
|
FpPrint *print = NULL;
|
|
fpi_device_get_delete_data (dev, &print);
|
|
|
|
FpFinger finger = fp_print_get_finger (print);
|
|
self->delete_finger_subtype = validity_finger_to_subtype (finger);
|
|
}
|
|
|
|
self->list_user_idx = 0;
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case DELETE_LOOKUP_USER:
|
|
{
|
|
/* Look up the user matching the print to delete.
|
|
* Iterate users to find one with a matching finger subtype.
|
|
* python-validity: db.lookup_user(identity) */
|
|
if (self->list_user_idx >= self->list_storage.user_count)
|
|
{
|
|
/* No matching finger found across all users */
|
|
fp_info ("Delete: no matching finger (subtype=%u) found in DB",
|
|
self->delete_finger_subtype);
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
|
return;
|
|
}
|
|
|
|
{
|
|
guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx];
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DELETE_LOOKUP_USER_RECV:
|
|
{
|
|
/* Parse user and look for the finger to delete */
|
|
if (self->cmd_response_status == VCSFW_STATUS_OK &&
|
|
self->cmd_response_data)
|
|
{
|
|
ValidityUser user = { 0 };
|
|
if (validity_db_parse_user (self->cmd_response_data,
|
|
self->cmd_response_len,
|
|
&user))
|
|
{
|
|
for (guint16 i = 0; i < user.finger_count; i++)
|
|
{
|
|
if (user.fingers[i].subtype == self->delete_finger_subtype)
|
|
{
|
|
/* Found matching finger — store dbid for deletion */
|
|
self->delete_finger_dbid = user.fingers[i].dbid;
|
|
validity_user_clear (&user);
|
|
fpi_ssm_next_state (ssm);
|
|
return;
|
|
}
|
|
}
|
|
validity_user_clear (&user);
|
|
}
|
|
}
|
|
|
|
/* Try next user — jump back to DELETE_LOOKUP_USER */
|
|
self->list_user_idx++;
|
|
fpi_ssm_jump_to_state (ssm, DELETE_LOOKUP_USER);
|
|
}
|
|
break;
|
|
|
|
case DELETE_DEL_RECORD:
|
|
{
|
|
/* Delete the finger record via cmd 0x48
|
|
* python-validity: db.del_record(dbid) */
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_del_record (
|
|
self->delete_finger_dbid, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case DELETE_DEL_RECORD_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
{
|
|
fp_warn ("del_record failed: status=0x%04x",
|
|
self->cmd_response_status);
|
|
fpi_ssm_mark_failed (ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
|
return;
|
|
}
|
|
|
|
fp_info ("Deleted finger record: dbid=%u", self->delete_finger_dbid);
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case DELETE_DONE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
delete_ssm_done (FpiSsm *ssm,
|
|
FpDevice *dev,
|
|
GError *error)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
validity_user_storage_clear (&self->list_storage);
|
|
fpi_device_delete_complete (dev, error);
|
|
}
|
|
|
|
void
|
|
validity_delete (FpDevice *device)
|
|
{
|
|
FpiSsm *ssm;
|
|
|
|
G_DEBUG_HERE ();
|
|
|
|
ssm = fpi_ssm_new (device, delete_run_state, DELETE_NUM_STATES);
|
|
fpi_ssm_start (ssm, delete_ssm_done);
|
|
}
|
|
|
|
/* ================================================================
|
|
* Clear storage — delete all fingerprint records from the sensor DB
|
|
* python-validity: for user in db.get_user_storage(): db.del_record(user.dbid)
|
|
* ================================================================ */
|
|
|
|
static void
|
|
clear_run_state (FpiSsm *ssm,
|
|
FpDevice *dev)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case CLEAR_GET_STORAGE:
|
|
{
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_get_user_storage (
|
|
VALIDITY_STORAGE_NAME, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case CLEAR_GET_STORAGE_RECV:
|
|
{
|
|
validity_user_storage_clear (&self->list_storage);
|
|
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK ||
|
|
!self->cmd_response_data ||
|
|
!validity_db_parse_user_storage (self->cmd_response_data,
|
|
self->cmd_response_len,
|
|
&self->list_storage))
|
|
{
|
|
/* No storage or parse error — nothing to clear */
|
|
fpi_ssm_jump_to_state (ssm, CLEAR_DONE);
|
|
return;
|
|
}
|
|
|
|
self->list_user_idx = 0;
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case CLEAR_DEL_USER:
|
|
{
|
|
if (self->list_user_idx >= self->list_storage.user_count)
|
|
{
|
|
fpi_ssm_jump_to_state (ssm, CLEAR_DONE);
|
|
return;
|
|
}
|
|
|
|
guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx];
|
|
|
|
gsize cmd_len;
|
|
guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len);
|
|
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
|
g_free (cmd);
|
|
}
|
|
break;
|
|
|
|
case CLEAR_DEL_USER_RECV:
|
|
{
|
|
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
|
fp_warn ("clear_storage: del_record(dbid=%u) failed: status=0x%04x",
|
|
self->list_storage.user_dbids[self->list_user_idx],
|
|
self->cmd_response_status);
|
|
|
|
self->list_user_idx++;
|
|
fpi_ssm_jump_to_state (ssm, CLEAR_DEL_USER);
|
|
}
|
|
break;
|
|
|
|
case CLEAR_DONE:
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clear_ssm_done (FpiSsm *ssm,
|
|
FpDevice *dev,
|
|
GError *error)
|
|
{
|
|
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
|
|
|
validity_user_storage_clear (&self->list_storage);
|
|
fpi_device_clear_storage_complete (dev, error);
|
|
}
|
|
|
|
void
|
|
validity_clear_storage (FpDevice *device)
|
|
{
|
|
FpiSsm *ssm;
|
|
|
|
G_DEBUG_HERE ();
|
|
|
|
ssm = fpi_ssm_new (device, clear_run_state, CLEAR_NUM_STATES);
|
|
fpi_ssm_start (ssm, clear_ssm_done);
|
|
}
|