libfprint/libfprint/drivers/validity/validity_verify.c
Leonardo Francisco f7ce74df1b validity: fix dead code, stubs, and broken logic across Iteration 6
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).
2026-04-10 22:18:43 +00:00

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);
}