From cca2b6a624a738e0334dfafee9426a594532c91c Mon Sep 17 00:00:00 2001 From: Haowei Lo Date: Mon, 25 Jul 2022 15:12:11 +0800 Subject: [PATCH] fpcmoc: Support FPC moc devices Supported PID: 0xFFE0/A305/D805/DA04/D205 --- data/autosuspend.hwdb | 9 + libfprint/drivers/fpcmoc/fpc.c | 1892 ++++++++++++++++++++++++++++++++ libfprint/drivers/fpcmoc/fpc.h | 221 ++++ libfprint/meson.build | 2 + meson.build | 1 + tests/fpcmoc/custom.pcapng | Bin 0 -> 25324 bytes tests/fpcmoc/custom.py | 86 ++ tests/fpcmoc/device | 234 ++++ tests/meson.build | 1 + 9 files changed, 2446 insertions(+) create mode 100644 libfprint/drivers/fpcmoc/fpc.c create mode 100644 libfprint/drivers/fpcmoc/fpc.h create mode 100644 tests/fpcmoc/custom.pcapng create mode 100755 tests/fpcmoc/custom.py create mode 100644 tests/fpcmoc/device diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index cd4fd4c0..d476f7e1 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -157,6 +157,15 @@ usb:v1C7Ap0603* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver fpcmoc +usb:v10A5pFFE0* +usb:v10A5pA305* +usb:v10A5pDA04* +usb:v10A5pD805* +usb:v10A5pD205* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver goodixmoc usb:v27C6p5840* usb:v27C6p6014* diff --git a/libfprint/drivers/fpcmoc/fpc.c b/libfprint/drivers/fpcmoc/fpc.c new file mode 100644 index 00000000..b64cc36e --- /dev/null +++ b/libfprint/drivers/fpcmoc/fpc.c @@ -0,0 +1,1892 @@ +/* + * Copyright (c) 2022 Fingerprint Cards AB + * + * 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 + */ + +#include "drivers_api.h" +#include "fpc.h" + +#define FP_COMPONENT "fpcmoc" +#define MAX_ENROLL_SAMPLES (25) +#define CTRL_TIMEOUT (1000) +#define DATA_TIMEOUT (5000) + +/* Usb port setting */ +#define EP_IN (1 | FPI_USB_ENDPOINT_IN) +#define EP_IN_MAX_BUF_SIZE (2048) + +struct _FpiDeviceFpcMoc +{ + FpDevice parent; + FpiSsm *task_ssm; + FpiSsm *cmd_ssm; + gboolean cmd_suspended; + gint enroll_stage; + gint immobile_stage; + gint max_enroll_stage; + gint max_immobile_stage; + gint max_stored_prints; + guint cmd_data_timeout; + guint8 *dbid; + gboolean do_cleanup; + GCancellable *interrupt_cancellable; +}; + +G_DEFINE_TYPE (FpiDeviceFpcMoc, fpi_device_fpcmoc, FP_TYPE_DEVICE); + +typedef void (*SynCmdMsgCallback) (FpiDeviceFpcMoc *self, + void *resp, + GError *error); + +typedef struct +{ + FpcCmdType cmdtype; + guint8 request; + guint16 value; + guint16 index; + guint8 *data; + gsize data_len; + SynCmdMsgCallback callback; +} CommandData; + +static const FpIdEntry id_table[] = { + { .vid = 0x10A5, .pid = 0xFFE0, }, + { .vid = 0x10A5, .pid = 0xA305, }, + { .vid = 0x10A5, .pid = 0xDA04, }, + { .vid = 0x10A5, .pid = 0xD805, }, + { .vid = 0x10A5, .pid = 0xD205, }, + /* terminating entry */ + { .vid = 0, .pid = 0, .driver_data = 0 }, +}; + +static void +fpc_suspend_resume_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer user_data, + GError *error) +{ + int ssm_state = fpi_ssm_get_cur_state (transfer->ssm); + + fp_dbg ("%s current ssm state: %d", G_STRFUNC, ssm_state); + + if (ssm_state == FP_CMD_SUSPENDED) + { + if (error) + fpi_ssm_mark_failed (transfer->ssm, error); + + fpi_device_suspend_complete (device, error); + /* The resume handler continues to the next state! */ + } + else if (ssm_state == FP_CMD_RESUME) + { + if (error) + fpi_ssm_mark_failed (transfer->ssm, error); + else + fpi_ssm_jump_to_state (transfer->ssm, FP_CMD_GET_DATA); + + fpi_device_resume_complete (device, error); + } +} + +static void +fpc_cmd_receive_cb (FpiUsbTransfer *transfer, + FpDevice *device, + gpointer user_data, + GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData *data = user_data; + int ssm_state = 0; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && (self->cmd_suspended)) + { + g_error_free (error); + fpi_ssm_jump_to_state (transfer->ssm, FP_CMD_SUSPENDED); + return; + } + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + if (data == NULL) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + ssm_state = fpi_ssm_get_cur_state (transfer->ssm); + fp_dbg ("%s current ssm state: %d", G_STRFUNC, ssm_state); + + if (data->cmdtype == FPC_CMDTYPE_TO_DEVICE) + { + /* It should not receive any data. */ + if (data->callback) + data->callback (self, NULL, NULL); + + fpi_ssm_mark_completed (transfer->ssm); + return; + } + else if (data->cmdtype == FPC_CMDTYPE_TO_DEVICE_EVTDATA) + { + if (ssm_state == FP_CMD_SEND) + { + fpi_ssm_next_state (transfer->ssm); + return; + } + + if (ssm_state == FP_CMD_GET_DATA) + { + fpc_cmd_response_t evt_data = {0}; + fp_dbg ("%s recv evt data length: %ld", G_STRFUNC, transfer->actual_length); + if (transfer->actual_length == 0) + { + fp_err ("%s Expect data but actual_length = 0", G_STRFUNC); + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + memcpy (&evt_data, transfer->buffer, transfer->actual_length); + + if (data->callback) + data->callback (self, (guint8 *) &evt_data, NULL); + + fpi_ssm_mark_completed (transfer->ssm); + return; + } + } + else if (data->cmdtype == FPC_CMDTYPE_FROM_DEVICE) + { + if (transfer->actual_length == 0) + { + fp_err ("%s Expect data but actual_length = 0", G_STRFUNC); + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + if (data->callback) + data->callback (self, transfer->buffer, NULL); + + fpi_ssm_mark_completed (transfer->ssm); + return; + } + else + { + fp_err ("%s incorrect cmdtype (%x) ", G_STRFUNC, data->cmdtype); + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + /* should not run here... */ + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); +} + +static void +fpc_send_ctrl_cmd (FpDevice *dev) +{ + FpiUsbTransfer *transfer = NULL; + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + CommandData *cmd_data = fpi_ssm_get_data (self->cmd_ssm); + FpcCmdType cmdtype = FPC_CMDTYPE_UNKNOWN; + + if (!cmd_data) + { + fp_err ("%s No cmd_data is set ", G_STRFUNC); + fpi_ssm_mark_failed (self->cmd_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + } + + cmdtype = cmd_data->cmdtype; + + if ((cmdtype != FPC_CMDTYPE_FROM_DEVICE) && cmd_data->data_len && + (cmd_data->data == NULL)) + { + fp_err ("%s data buffer is null but len is not! ", G_STRFUNC); + fpi_ssm_mark_failed (self->cmd_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + } + + if (cmdtype == FPC_CMDTYPE_UNKNOWN) + { + fp_err ("%s unknown cmd type ", G_STRFUNC); + fpi_ssm_mark_failed (self->cmd_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + } + + fp_dbg ("%s CMD: 0x%x, value: 0x%x, index: %x type: %d", G_STRFUNC, + cmd_data->request, cmd_data->value, cmd_data->index, cmdtype); + + transfer = fpi_usb_transfer_new (dev); + fpi_usb_transfer_fill_control (transfer, + ((cmdtype == FPC_CMDTYPE_FROM_DEVICE) ? + (G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) : + (G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE)), + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + cmd_data->request, + cmd_data->value, + cmd_data->index, + cmd_data->data_len); + + transfer->ssm = self->cmd_ssm; + if (cmdtype != FPC_CMDTYPE_FROM_DEVICE && + cmd_data->data != NULL && + cmd_data->data_len != 0) + memcpy (transfer->buffer, cmd_data->data, cmd_data->data_len); + + fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, + fpc_cmd_receive_cb, + fpi_ssm_get_data (transfer->ssm)); +} + +static void +fpc_cmd_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + CommandData *data = fpi_ssm_get_data (ssm); + + /* Notify about the SSM failure from here instead. */ + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + if (data->callback) + data->callback (self, NULL, error); + } + + self->cmd_ssm = NULL; +} + +static void +fpc_cmd_run_state (FpiSsm *ssm, + FpDevice *dev) +{ + FpiUsbTransfer *transfer = NULL; + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_CMD_SEND: + fpc_send_ctrl_cmd (dev); + break; + + case FP_CMD_GET_DATA: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, EP_IN, EP_IN_MAX_BUF_SIZE); + fpi_usb_transfer_submit (transfer, + self->cmd_data_timeout, + self->interrupt_cancellable, + fpc_cmd_receive_cb, + fpi_ssm_get_data (ssm)); + break; + + case FP_CMD_SUSPENDED: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + FPC_CMD_INDICATE_S_STATE, + FPC_HOST_MS_SX, + 0, + 0); + + fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, + fpc_suspend_resume_cb, NULL); + break; + + case FP_CMD_RESUME: + transfer = fpi_usb_transfer_new (dev); + transfer->ssm = ssm; + fpi_usb_transfer_fill_control (transfer, + G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE, + G_USB_DEVICE_REQUEST_TYPE_VENDOR, + G_USB_DEVICE_RECIPIENT_DEVICE, + FPC_CMD_INDICATE_S_STATE, + FPC_HOST_MS_S0, + 0, + 0); + + fpi_usb_transfer_submit (transfer, CTRL_TIMEOUT, NULL, + fpc_suspend_resume_cb, NULL); + break; + } + +} + +static void +fpc_sensor_cmd (FpiDeviceFpcMoc *self, + gboolean wait_data_delay, + CommandData *cmd_data) +{ + CommandData *data = NULL; + + g_return_if_fail (cmd_data); + g_return_if_fail (cmd_data->cmdtype != FPC_CMDTYPE_UNKNOWN); + + data = g_memdup2 (cmd_data, sizeof (CommandData)); + + if (wait_data_delay) + { + self->cmd_data_timeout = 0; + g_set_object (&self->interrupt_cancellable, g_cancellable_new ()); + } + else + { + self->cmd_data_timeout = DATA_TIMEOUT; + g_clear_object (&self->interrupt_cancellable); + } + + self->cmd_ssm = fpi_ssm_new (FP_DEVICE (self), + fpc_cmd_run_state, + FP_CMD_NUM_STATES); + + fpi_ssm_set_data (self->cmd_ssm, data, g_free); + fpi_ssm_start (self->cmd_ssm, fpc_cmd_ssm_done); +} + +static void +fpc_dev_release_interface (FpiDeviceFpcMoc *self, + GError *error) +{ + g_autoptr(GError) release_error = NULL; + + /* Release usb interface */ + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (self)), + 0, 0, &release_error); + /* Retain passed error if set, otherwise propagate error from release. */ + if (error) + { + fpi_device_close_complete (FP_DEVICE (self), error); + return; + } + + /* Notify close complete */ + fpi_device_close_complete (FP_DEVICE (self), release_error); +} + +static gboolean +check_data (void *data, GError **error) +{ + if (*error != NULL) + return FALSE; + + if (data == NULL) + { + g_propagate_error (error, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return FALSE; + } + + return TRUE; +} + +static void +fpc_evt_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + pfpc_cmd_response_t presp = NULL; + + if (!check_data (data, &error)) + { + fp_err ("%s error: %s", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + presp = (pfpc_cmd_response_t) data; + + switch (presp->evt_hdr.cmdid) + { + case FPC_EVT_FID_DATA: + fp_dbg ("%s Enum Fids: status = %d, NumFids = %d", G_STRFUNC, + presp->evt_enum_fids.status, presp->evt_enum_fids.num_ids); + if (presp->evt_enum_fids.status || (presp->evt_enum_fids.num_ids > FPC_TEMPLATES_MAX)) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "Get Fids failed")); + return; + } + break; + + case FPC_EVT_INIT_RESULT: + fp_dbg ("%s INIT: status=%d, Sensor = %d, HWID = 0x%04X, WxH = %d x %d", G_STRFUNC, + presp->evt_inited.hdr.status, presp->evt_inited.sensor, + presp->evt_inited.hw_id, presp->evt_inited.img_w, presp->evt_inited.img_h); + + fp_dbg ("%s INIT: FW version: %s", G_STRFUNC, (gchar *) presp->evt_inited.fw_version); + break; + + case FPC_EVT_FINGER_DWN: + fp_dbg ("%s Got finger down event", G_STRFUNC); + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_PRESENT, + FP_FINGER_STATUS_NONE); + break; + + case FPC_EVT_IMG: + fp_dbg ("%s Got capture event", G_STRFUNC); + fpi_device_report_finger_status_changes (FP_DEVICE (self), + FP_FINGER_STATUS_NONE, + FP_FINGER_STATUS_PRESENT); + break; + + default: + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Unknown Evt (0x%x)!", presp->evt_hdr.cmdid)); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fpc_do_abort_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_dbg ("%s Do abort for reasons", G_STRFUNC); + fpi_ssm_next_state (self->task_ssm); +} + +static void +fpc_do_cleanup_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fp_dbg ("%s Do cleanup for reasons", G_STRFUNC); + self->do_cleanup = FALSE; + fpi_ssm_next_state (self->task_ssm); +} + +static void +fpc_template_delete_cb (FpiDeviceFpcMoc *self, + void *resp, + GError *error) +{ + FpDevice *device = FP_DEVICE (self); + + fpi_device_delete_complete (device, error); +} + +static gboolean +parse_print_data (GVariant *data, + guint8 *finger, + const guint8 **user_id, + gsize *user_id_len) +{ + g_autoptr(GVariant) user_id_var = NULL; + + g_return_val_if_fail (data, FALSE); + g_return_val_if_fail (finger, FALSE); + g_return_val_if_fail (user_id, FALSE); + g_return_val_if_fail (user_id_len, FALSE); + + *user_id = NULL; + *user_id_len = 0; + *finger = FPC_SUBTYPE_NOINFORMATION; + + if (!g_variant_check_format_string (data, "(y@ay)", FALSE)) + return FALSE; + + g_variant_get (data, + "(y@ay)", + finger, + &user_id_var); + + *user_id = g_variant_get_fixed_array (user_id_var, user_id_len, 1); + + if (*user_id_len == 0 || *user_id_len > SECURITY_MAX_SID_SIZE) + return FALSE; + + if (*user_id_len <= 0 || *user_id[0] == '\0' || *user_id[0] == ' ') + return FALSE; + + if (*finger != FPC_SUBTYPE_RESERVED) + return FALSE; + + return TRUE; +} + +/****************************************************************************** + * + * fpc_template_xxx Function + * + *****************************************************************************/ +static FpPrint * +fpc_print_from_data (FpiDeviceFpcMoc *self, fpc_fid_data_t *fid_data) +{ + FpPrint *print = NULL; + GVariant *data; + GVariant *uid; + g_autofree gchar *userid = NULL; + + userid = g_strndup ((gchar *) fid_data->identity, fid_data->identity_size); + print = fp_print_new (FP_DEVICE (self)); + + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + fid_data->identity, + fid_data->identity_size, + 1); + + data = g_variant_new ("(y@ay)", + fid_data->subfactor, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", data, NULL); + g_object_set (print, "description", userid, NULL); + fpi_print_fill_from_user_id (print, userid); + + return print; +} + +static void +fpc_template_list_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + g_autoptr(GPtrArray) list_result = NULL; + FpDevice *device = FP_DEVICE (self); + pfpc_cmd_response_t presp = NULL; + + if (error) + { + fpi_device_list_complete (FP_DEVICE (self), NULL, error); + return; + } + + if (data == NULL) + { + fpi_device_list_complete (FP_DEVICE (self), + NULL, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "Data is null")); + return; + } + + presp = (pfpc_cmd_response_t) data; + if (presp->evt_hdr.cmdid != FPC_EVT_FID_DATA) + { + fpi_device_list_complete (FP_DEVICE (self), + NULL, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID, + "Recv evt is incorrect: 0x%x", + presp->evt_hdr.cmdid)); + return; + } + + if (presp->evt_enum_fids.num_ids > FPC_TEMPLATES_MAX) + { + fpi_device_list_complete (FP_DEVICE (self), + NULL, + fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_FULL, + "Database is full")); + return; + } + + list_result = g_ptr_array_new_with_free_func (g_object_unref); + + if (presp->evt_enum_fids.num_ids == 0) + { + fp_info ("Database is empty"); + fpi_device_list_complete (device, + g_steal_pointer (&list_result), + NULL); + return; + } + + for (int n = 0; n < presp->evt_enum_fids.num_ids; n++) + { + FpPrint *print = NULL; + fpc_fid_data_t *fid_data = &presp->evt_enum_fids.fid_data[n]; + + if ((fid_data->subfactor != FPC_SUBTYPE_RESERVED) && + (fid_data->identity_type != FPC_IDENTITY_TYPE_RESERVED)) + { + fp_info ("Incompatible template found (0x%x, 0x%x)", + fid_data->subfactor, fid_data->identity_type); + continue; + } + + print = fpc_print_from_data (self, fid_data); + + g_ptr_array_add (list_result, g_object_ref_sink (print)); + } + + fp_info ("Query templates complete!"); + fpi_device_list_complete (device, + g_steal_pointer (&list_result), + NULL); +} + +/****************************************************************************** + * + * fpc_enroll_xxxx Function + * + *****************************************************************************/ + +static void +fpc_enroll_create_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + FPC_BEGIN_ENROL *presp = NULL; + + if (!check_data (data, &error)) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + presp = (FPC_BEGIN_ENROL *) data; + if (presp->status != 0) + { + error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "End Enroll failed: %d", + presp->status); + } + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + else + { + self->do_cleanup = TRUE; + fpi_ssm_next_state (self->task_ssm); + } +} + +static void +fpc_enroll_update_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + FPC_ENROL *presp = NULL; + + if (!check_data (data, &error)) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + presp = (FPC_ENROL *) data; + fp_dbg ("Enrol Update status: %d, remaining: %d", presp->status, presp->remaining); + switch (presp->status) + { + case FPC_ENROL_STATUS_FAILED_COULD_NOT_COMPLETE: + error = fpi_device_error_new (FP_DEVICE_ERROR_GENERAL); + break; + + case FPC_ENROL_STATUS_FAILED_ALREADY_ENROLED: + error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE); + break; + + case FPC_ENROL_STATUS_COMPLETED: + self->enroll_stage++; + fpi_device_enroll_progress (FP_DEVICE (self), self->enroll_stage, NULL, NULL); + fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_COMPLETE); + return; + + case FPC_ENROL_STATUS_IMAGE_TOO_SIMILAR: + fp_dbg ("Sample overlapping ratio is too High"); + /* here should tips remove finger and try again */ + if (self->max_immobile_stage) + { + if (self->immobile_stage >= self->max_immobile_stage) + { + fp_dbg ("Skip similar handle due to customer enrollment %d(%d)", + self->immobile_stage, self->max_immobile_stage); + /* Skip too similar handle, treat as normal enroll progress. */ + fpi_ssm_jump_to_state (self->task_ssm, FPC_ENROL_STATUS_PROGRESS); + break; + } + self->immobile_stage++; + } + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER)); + break; + + case FPC_ENROL_STATUS_PROGRESS: + self->enroll_stage++; + fpi_device_enroll_progress (FP_DEVICE (self), self->enroll_stage, NULL, NULL); + /* Used for customer enrollment scheme */ + if (self->enroll_stage >= (self->max_enroll_stage - self->max_immobile_stage)) + fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_COMPLETE); + break; + + case FPC_ENROL_STATUS_IMAGE_LOW_COVERAGE: + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER)); + break; + + case FPC_ENROL_STATUS_IMAGE_LOW_QUALITY: + fpi_device_enroll_progress (FP_DEVICE (self), + self->enroll_stage, + NULL, + fpi_device_retry_new (FP_DEVICE_RETRY_TOO_SHORT)); + break; + + default: + fp_err ("%s Unknown result code: %d ", G_STRFUNC, presp->status); + error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Enroll failed: %d", + presp->status); + break; + } + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + else + { + fpi_ssm_jump_to_state (self->task_ssm, FP_ENROLL_CAPTURE); + } +} + +static void +fpc_enroll_complete_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + FPC_END_ENROL *presp = NULL; + + self->do_cleanup = FALSE; + + if (check_data (data, &error)) + { + presp = (FPC_END_ENROL *) data; + if (presp->status != 0) + { + error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "End Enroll failed: %d", + presp->status); + } + else + { + fp_dbg ("Enrol End status: %d, fid: 0x%x", + presp->status, presp->fid); + } + } + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + else + { + fpi_ssm_next_state (self->task_ssm); + } +} + +static void +fpc_enroll_check_duplicate_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + FPC_IDENTIFY *presp = NULL; + + if (check_data (data, &error)) + { + presp = (FPC_IDENTIFY *) data; + if ((presp->status == 0) && (presp->subfactor == FPC_SUBTYPE_RESERVED) && + (presp->identity_type == FPC_IDENTITY_TYPE_RESERVED) && + (presp->identity_size <= SECURITY_MAX_SID_SIZE)) + { + fp_info ("%s Got a duplicated template", G_STRFUNC); + error = fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE); + } + } + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + else + { + fpi_ssm_next_state (self->task_ssm); + } + +} + +static void +fpc_enroll_bindid_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_ssm_next_state (self->task_ssm); +} + +static void +fpc_enroll_commit_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + gint32 *result = NULL; + + if (check_data (data, &error)) + { + result = (gint32 *) data; + if (*result != 0) + { + error = fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_FULL, + "Save DB failed: %d", + *result); + } + } + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + else + { + fpi_ssm_mark_completed (self->task_ssm); + } +} + + +static void +fpc_enroll_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData cmd_data = {0}; + gsize recv_data_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_ENROLL_ENUM: + { + FPC_FID_DATA pquery_data = {0}; + gsize query_data_len = 0; + guint32 wildcard_value = FPC_IDENTITY_WILDCARD; + query_data_len = sizeof (FPC_FID_DATA); + pquery_data.identity_type = FPC_IDENTITY_TYPE_WILDCARD; + pquery_data.reserved = 16; + pquery_data.identity_size = sizeof (wildcard_value); + pquery_data.subfactor = (guint32) FPC_SUBTYPE_ANY; + memcpy (&pquery_data.data[0], + &wildcard_value, pquery_data.identity_size); + + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_ENUM; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &pquery_data; + cmd_data.data_len = query_data_len; + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_CREATE: + { + recv_data_len = sizeof (FPC_BEGIN_ENROL); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_BEGIN_ENROL; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_enroll_create_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_CAPTURE: + { + guint32 capture_id = FPC_CAPTUREID_RESERVED; + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_ARM; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &capture_id; + cmd_data.data_len = sizeof (guint32); + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, TRUE, &cmd_data); + } + break; + + case FP_ENROLL_GET_IMG: + { + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_GET_IMG; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = 0; + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_UPDATE: + { + recv_data_len = sizeof (FPC_ENROL); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_ENROL; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_enroll_update_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_COMPLETE: + { + recv_data_len = sizeof (FPC_END_ENROL); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_END_ENROL; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_enroll_complete_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_CHECK_DUPLICATE: + { + recv_data_len = sizeof (FPC_IDENTIFY); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_IDENTIFY; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_enroll_check_duplicate_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_BINDID: + { + FPC_FID_DATA data = {0}; + gsize data_len = 0; + FpPrint *print = NULL; + GVariant *fpi_data = NULL; + GVariant *uid = NULL; + guint finger = FPC_SUBTYPE_RESERVED; + g_autofree gchar *user_id = NULL; + gssize user_id_len; + g_autofree guint8 *payload = NULL; + + fpi_device_get_enroll_data (device, &print); + + user_id = fpi_print_generate_user_id (print); + + user_id_len = strlen (user_id); + user_id_len = MIN (SECURITY_MAX_SID_SIZE, user_id_len); + + uid = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE, + user_id, + user_id_len, + 1); + fpi_data = g_variant_new ("(y@ay)", + finger, + uid); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + g_object_set (print, "fpi-data", fpi_data, NULL); + g_object_set (print, "description", user_id, NULL); + + fp_dbg ("user_id: %s, finger: 0x%x", user_id, finger); + + data_len = sizeof (FPC_FID_DATA); + data.identity_type = FPC_IDENTITY_TYPE_RESERVED; + data.reserved = 16; + data.identity_size = user_id_len; + data.subfactor = (guint32) finger; + memcpy (&data.data[0], + user_id, user_id_len); + + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_BIND_IDENTITY; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &data; + cmd_data.data_len = data_len; + cmd_data.callback = fpc_enroll_bindid_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_COMMIT: + { + recv_data_len = sizeof (gint32); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_STORE_DB; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_enroll_commit_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_DICARD: + { + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_ABORT; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = 0; + cmd_data.callback = fpc_do_abort_cb; + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_ENROLL_CLEANUP: + { + if (self->do_cleanup == TRUE) + { + recv_data_len = sizeof (FPC_END_ENROL); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_END_ENROL; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_do_cleanup_cb; + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + else + { + fpi_ssm_next_state (self->task_ssm); + } + } + break; + } +} + +static void +fpc_enroll_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + FpPrint *print = NULL; + + fp_info ("Enrollment complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error) + { + fpi_device_enroll_complete (dev, NULL, error); + self->task_ssm = NULL; + return; + } + + fpi_device_get_enroll_data (FP_DEVICE (self), &print); + fpi_device_enroll_complete (FP_DEVICE (self), g_object_ref (print), NULL); + self->task_ssm = NULL; +} + +/****************************************************************************** + * + * fpc_verify_xxx function + * + *****************************************************************************/ + +static void +fpc_verify_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + g_autoptr(GPtrArray) templates = NULL; + FpDevice *device = FP_DEVICE (self); + gboolean found = FALSE; + FpiDeviceAction current_action; + FPC_IDENTIFY *presp = NULL; + + if (!check_data (data, &error)) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + presp = (FPC_IDENTIFY *) data; + current_action = fpi_device_get_current_action (device); + + g_assert (current_action == FPI_DEVICE_ACTION_VERIFY || + current_action == FPI_DEVICE_ACTION_IDENTIFY); + + if ((presp->status == 0) && (presp->subfactor == FPC_SUBTYPE_RESERVED) && + (presp->identity_type == FPC_IDENTITY_TYPE_RESERVED) && + (presp->identity_size <= SECURITY_MAX_SID_SIZE)) + { + FpPrint *match = NULL; + FpPrint *print = NULL; + gint cnt = 0; + fpc_fid_data_t fid_data = {0}; + + fid_data.subfactor = presp->subfactor; + fid_data.identity_type = presp->identity_type; + fid_data.identity_size = presp->identity_size; + memcpy (fid_data.identity, &presp->data[0], + fid_data.identity_size); + + match = fpc_print_from_data (self, &fid_data); + + if (current_action == FPI_DEVICE_ACTION_VERIFY) + { + templates = g_ptr_array_sized_new (1); + fpi_device_get_verify_data (device, &print); + g_ptr_array_add (templates, print); + } + else + { + fpi_device_get_identify_data (device, &templates); + g_ptr_array_ref (templates); + } + + for (cnt = 0; cnt < templates->len; cnt++) + { + print = g_ptr_array_index (templates, cnt); + + if (fp_print_equal (print, match)) + { + found = TRUE; + break; + } + } + + if (found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_SUCCESS, match, error); + else + fpi_device_identify_report (device, print, match, error); + + fpi_ssm_mark_completed (self->task_ssm); + return; + } + } + + if (!found) + { + if (current_action == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, error); + else + fpi_device_identify_report (device, NULL, NULL, error); + } + + /* This is the last state for verify/identify */ + fpi_ssm_mark_completed (self->task_ssm); +} + +static void +fpc_verify_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData cmd_data = {0}; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_VERIFY_CAPTURE: + { + guint32 capture_id = FPC_CAPTUREID_RESERVED; + fpi_device_report_finger_status_changes (device, + FP_FINGER_STATUS_NEEDED, + FP_FINGER_STATUS_NONE); + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_ARM; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &capture_id; + cmd_data.data_len = sizeof (guint32); + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, TRUE, &cmd_data); + } + break; + + case FP_VERIFY_GET_IMG: + { + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_GET_IMG; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = 0; + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_VERIFY_IDENTIFY: + { + gsize recv_data_len = sizeof (FPC_IDENTIFY); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_IDENTIFY; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_verify_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + + case FP_VERIFY_CANCEL: + { + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_ABORT; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = 0; + cmd_data.callback = fpc_do_abort_cb; + fpc_sensor_cmd (self, FALSE, &cmd_data); + + } + break; + } +} + +static void +fpc_verify_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + + fp_info ("Verify_identify complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + if (error && error->domain == FP_DEVICE_RETRY) + { + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_report (dev, FPI_MATCH_ERROR, NULL, g_steal_pointer (&error)); + else + fpi_device_identify_report (dev, NULL, NULL, g_steal_pointer (&error)); + } + + if (fpi_device_get_current_action (dev) == FPI_DEVICE_ACTION_VERIFY) + fpi_device_verify_complete (dev, error); + else + fpi_device_identify_complete (dev, error); + + self->task_ssm = NULL; +} + +/****************************************************************************** + * + * fpc_init_xxx function + * + *****************************************************************************/ +static void +fpc_clear_storage_cb (FpiDeviceFpcMoc *self, + void *resp, + GError *error) +{ + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + fpi_ssm_next_state (self->task_ssm); + +} + +static void +fpc_clear_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData cmd_data = {0}; + FPC_DB_OP data = {0}; + gsize data_len = 0; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_CLEAR_DELETE_DB: + { + if (self->dbid) + { + data_len = sizeof (FPC_DB_OP); + data.database_id_size = FPC_DB_ID_LEN; + data.reserved = 8; + memcpy (&data.data[0], self->dbid, FPC_DB_ID_LEN); + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_DELETE_DB; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &data; + cmd_data.data_len = data_len; + cmd_data.callback = fpc_clear_storage_cb; + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, + "No DBID found")); + } + + } + break; + + case FP_CLEAR_CREATE_DB: + { + if (self->dbid) + { + data_len = sizeof (FPC_DB_OP); + data.database_id_size = FPC_DB_ID_LEN; + data.reserved = 8; + memcpy (&data.data[0], self->dbid, FPC_DB_ID_LEN); + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_LOAD_DB; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &data; + cmd_data.data_len = data_len; + cmd_data.callback = fpc_clear_storage_cb; + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + else + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED, + "No DBID found")); + } + } + break; + } +} + +static void +fpc_clear_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + + fp_info ("Clear Storage complete!"); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_clear_storage_complete (dev, error); + self->task_ssm = NULL; +} + +/****************************************************************************** + * + * fpc_init_xxx function + * + *****************************************************************************/ + +static void +fpc_init_load_db_cb (FpiDeviceFpcMoc *self, + void *data, + GError *error) +{ + FPC_LOAD_DB *presp = NULL; + + if (error) + { + fp_err ("%s error: %s ", G_STRFUNC, error->message); + fpi_ssm_mark_failed (self->task_ssm, error); + return; + } + + if (data == NULL) + { + fpi_ssm_mark_failed (self->task_ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + presp = (FPC_LOAD_DB *) data; + if (presp->status) + { + fp_err ("%s Load DB failed: %d - Expect to create a new one", G_STRFUNC, presp->status); + fpi_ssm_next_state (self->task_ssm); + return; + } + + g_clear_pointer (&self->dbid, g_free); + self->dbid = g_memdup2 (presp->data, FPC_DB_ID_LEN); + if (self->dbid == NULL) + { + fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + fp_dbg ("%s got dbid size: %d", G_STRFUNC, presp->database_id_size); + fp_dbg ("%s dbid: 0x%02x%02x%02x%02x-%02x%02x-%02x%02x-" \ + "%02x%02x-%02x%02x%02x%02x%02x%02x", + G_STRFUNC, + presp->data[0], presp->data[1], + presp->data[2], presp->data[3], + presp->data[4], presp->data[5], + presp->data[6], presp->data[7], + presp->data[8], presp->data[9], + presp->data[10], presp->data[11], + presp->data[12], presp->data[13], + presp->data[14], presp->data[15]); + fpi_ssm_mark_completed (self->task_ssm); +} + +static void +fpc_init_sm_run_state (FpiSsm *ssm, FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + guint32 session_id = FPC_SESSIONID_RESERVED; + CommandData cmd_data = {0}; + + switch (fpi_ssm_get_cur_state (ssm)) + { + case FP_INIT: + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_INIT; + cmd_data.value = 0x1; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &session_id; + cmd_data.data_len = sizeof (session_id); + cmd_data.callback = fpc_evt_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + break; + + case FP_LOAD_DB: + { + gsize recv_data_len = sizeof (FPC_LOAD_DB); + cmd_data.cmdtype = FPC_CMDTYPE_FROM_DEVICE; + cmd_data.request = FPC_CMD_LOAD_DB; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = NULL; + cmd_data.data_len = recv_data_len; + cmd_data.callback = fpc_init_load_db_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + } + break; + } +} + +static void +fpc_init_ssm_done (FpiSsm *ssm, FpDevice *dev, GError *error) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (dev); + + if (fpi_ssm_get_error (ssm)) + error = fpi_ssm_get_error (ssm); + + fpi_device_open_complete (dev, error); + self->task_ssm = NULL; +} + +/****************************************************************************** + * + * Interface Function + * + *****************************************************************************/ + +static void +fpc_dev_probe (FpDevice *device) +{ + GUsbDevice *usb_dev; + GError *error = NULL; + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + g_autofree gchar *product = NULL; + gint product_id = 0; + + fp_dbg ("%s enter --> ", G_STRFUNC); + + /* Claim usb interface */ + usb_dev = fpi_device_get_usb_device (device); + if (!g_usb_device_open (usb_dev, &error)) + { + fp_dbg ("%s g_usb_device_open failed %s", G_STRFUNC, error->message); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_reset (usb_dev, &error)) + { + fp_dbg ("%s g_usb_device_reset failed %s", G_STRFUNC, error->message); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + if (!g_usb_device_claim_interface (usb_dev, 0, 0, &error)) + { + fp_dbg ("%s g_usb_device_claim_interface failed %s", G_STRFUNC, error->message); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + product = g_usb_device_get_string_descriptor (usb_dev, + g_usb_device_get_product_index (usb_dev), + &error); + if (product) + fp_dbg ("Device name: %s", product); + + if (error) + { + fp_dbg ("%s g_usb_device_get_string_descriptor failed %s", G_STRFUNC, error->message); + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), + 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, NULL, error); + return; + } + + product_id = g_usb_device_get_pid (usb_dev); + /* Reserved for customer enroll scheme */ + self->max_immobile_stage = 0; /* By default is not customer enrollment */ + switch (product_id) + { + case 0xFFE0: + case 0xA305: + case 0xD805: + case 0xDA04: + case 0xD205: + self->max_enroll_stage = MAX_ENROLL_SAMPLES; + break; + + default: + fp_warn ("Device %x is not supported", product_id); + self->max_enroll_stage = MAX_ENROLL_SAMPLES; + break; + } + fpi_device_set_nr_enroll_stages (device, self->max_enroll_stage); + g_usb_device_release_interface (fpi_device_get_usb_device (FP_DEVICE (device)), + 0, 0, NULL); + g_usb_device_close (usb_dev, NULL); + fpi_device_probe_complete (device, NULL, product, error); +} + +static void +fpc_dev_open (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + GError *error = NULL; + + fp_dbg ("%s enter -->", G_STRFUNC); + if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + /* Claim usb interface */ + if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error)) + { + fpi_device_open_complete (FP_DEVICE (self), error); + return; + } + + self->task_ssm = fpi_ssm_new (device, fpc_init_sm_run_state, + FP_INIT_NUM_STATES); + + fpi_ssm_start (self->task_ssm, fpc_init_ssm_done); +} + +static void +fpc_dev_close (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + g_clear_pointer (&self->dbid, g_free); + g_clear_object (&self->interrupt_cancellable); + fpc_dev_release_interface (self, NULL); +} + +static void +fpc_dev_verify_identify (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + self->task_ssm = fpi_ssm_new_full (device, fpc_verify_sm_run_state, + FP_VERIFY_NUM_STATES, + FP_VERIFY_CANCEL, + "verify_identify"); + + fpi_ssm_start (self->task_ssm, fpc_verify_ssm_done); +} + +static void +fpc_dev_enroll (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + + self->enroll_stage = 0; + self->immobile_stage = 0; + self->task_ssm = fpi_ssm_new_full (device, fpc_enroll_sm_run_state, + FP_ENROLL_NUM_STATES, + FP_ENROLL_DICARD, + "enroll"); + + fpi_ssm_start (self->task_ssm, fpc_enroll_ssm_done); +} + +static void +fpc_dev_template_list (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData cmd_data = {0}; + FPC_FID_DATA pquery_data = {0}; + gsize query_data_len = 0; + guint32 wildcard_value = FPC_IDENTITY_WILDCARD; + + fp_dbg ("%s enter -->", G_STRFUNC); + + query_data_len = sizeof (FPC_FID_DATA); + pquery_data.identity_type = FPC_IDENTITY_TYPE_WILDCARD; + pquery_data.reserved = 16; + pquery_data.identity_size = sizeof (wildcard_value); + pquery_data.subfactor = (guint32) FPC_SUBTYPE_ANY; + memcpy (&pquery_data.data[0], + &wildcard_value, pquery_data.identity_size); + + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE_EVTDATA; + cmd_data.request = FPC_CMD_ENUM; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &pquery_data; + cmd_data.data_len = query_data_len; + cmd_data.callback = fpc_template_list_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); +} + +static void +fpc_dev_suspend (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + FpiDeviceAction action = fpi_device_get_current_action (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + fpi_device_suspend_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + g_assert (self->cmd_ssm); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == FP_CMD_GET_DATA); + self->cmd_suspended = TRUE; + g_cancellable_cancel (self->interrupt_cancellable); +} + +static void +fpc_dev_resume (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + FpiDeviceAction action = fpi_device_get_current_action (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + + if (action != FPI_DEVICE_ACTION_VERIFY && action != FPI_DEVICE_ACTION_IDENTIFY) + { + g_assert_not_reached (); + fpi_device_resume_complete (device, fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + g_assert (self->cmd_ssm); + g_assert (self->cmd_suspended); + g_assert (fpi_ssm_get_cur_state (self->cmd_ssm) == FP_CMD_SUSPENDED); + self->cmd_suspended = FALSE; + g_set_object (&self->interrupt_cancellable, g_cancellable_new ()); + fpi_ssm_jump_to_state (self->cmd_ssm, FP_CMD_RESUME); +} + +static void +fpc_dev_cancel (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + g_cancellable_cancel (self->interrupt_cancellable); +} + +static void +fpc_dev_template_delete (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + CommandData cmd_data = {0}; + FpPrint *print = NULL; + + g_autoptr(GVariant) fpi_data = NULL; + FPC_FID_DATA data = {0}; + gsize data_len = 0; + guint8 finger = FPC_SUBTYPE_NOINFORMATION; + const guint8 *user_id; + gsize user_id_len = 0; + + fp_dbg ("%s enter -->", G_STRFUNC); + + fpi_device_get_delete_data (device, &print); + + g_object_get (print, "fpi-data", &fpi_data, NULL); + + if (!parse_print_data (fpi_data, &finger, &user_id, &user_id_len)) + { + fpi_device_delete_complete (device, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID)); + return; + } + + data_len = sizeof (FPC_FID_DATA); + data.identity_type = FPC_IDENTITY_TYPE_RESERVED; + data.reserved = 16; + data.identity_size = user_id_len; + data.subfactor = (guint32) finger; + memcpy (&data.data[0], user_id, user_id_len); + cmd_data.cmdtype = FPC_CMDTYPE_TO_DEVICE; + cmd_data.request = FPC_CMD_DELETE_TEMPLATE; + cmd_data.value = 0x0; + cmd_data.index = 0x0; + cmd_data.data = (guint8 *) &data; + cmd_data.data_len = data_len; + cmd_data.callback = fpc_template_delete_cb; + + fpc_sensor_cmd (self, FALSE, &cmd_data); + fp_dbg ("%s exit <--", G_STRFUNC); +} + +static void +fpc_dev_clear_storage (FpDevice *device) +{ + FpiDeviceFpcMoc *self = FPI_DEVICE_FPCMOC (device); + + fp_dbg ("%s enter -->", G_STRFUNC); + self->task_ssm = fpi_ssm_new_full (device, fpc_clear_sm_run_state, + FP_CLEAR_NUM_STATES, + FP_CLEAR_NUM_STATES, + "Clear storage"); + + fpi_ssm_start (self->task_ssm, fpc_clear_ssm_done); +} + +static void +fpi_device_fpcmoc_init (FpiDeviceFpcMoc *self) +{ + fp_dbg ("%s enter -->", G_STRFUNC); + G_DEBUG_HERE (); +} + +static void +fpi_device_fpcmoc_class_init (FpiDeviceFpcMocClass *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "FPC MOC 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 = MAX_ENROLL_SAMPLES; + dev_class->temp_hot_seconds = -1; + + dev_class->open = fpc_dev_open; + dev_class->close = fpc_dev_close; + dev_class->probe = fpc_dev_probe; + dev_class->enroll = fpc_dev_enroll; + dev_class->delete = fpc_dev_template_delete; + dev_class->list = fpc_dev_template_list; + dev_class->verify = fpc_dev_verify_identify; + dev_class->identify = fpc_dev_verify_identify; + dev_class->suspend = fpc_dev_suspend; + dev_class->resume = fpc_dev_resume; + dev_class->clear_storage = fpc_dev_clear_storage; + dev_class->cancel = fpc_dev_cancel; + + fpi_device_class_auto_initialize_features (dev_class); + dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK; +} diff --git a/libfprint/drivers/fpcmoc/fpc.h b/libfprint/drivers/fpcmoc/fpc.h new file mode 100644 index 00000000..389c63ff --- /dev/null +++ b/libfprint/drivers/fpcmoc/fpc.h @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2022 Fingerprint Cards AB + * + * 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 + */ + +#pragma once + +#include "fpi-device.h" +#include "fpi-ssm.h" + +#include +#include + +#define TEMPLATE_ID_SIZE (32) +#define MAX_FW_VERSION_STR_LEN (16) +#define FPC_CMD_INIT (0x01) +#define FPC_CMD_ARM (0x02) +#define FPC_CMD_ABORT (0x03) +#define FPC_CMD_INDICATE_S_STATE (0x08) +#define FPC_CMD_GET_IMG (0x09) +#define FPC_CMD_GET_KPI (0x0C) +#define FPC_CMD_LOAD_DB (0x60) +#define FPC_CMD_STORE_DB (0x61) +#define FPC_CMD_DELETE_DB (0x62) +#define FPC_CMD_DELETE_TEMPLATE (0x63) +#define FPC_CMD_BEGIN_ENROL (0x67) +#define FPC_CMD_ENROL (0x68) +#define FPC_CMD_END_ENROL (0x69) +#define FPC_CMD_BIND_IDENTITY (0x6A) +#define FPC_CMD_IDENTIFY (0x6B) +#define FPC_CMD_ENUM (0x70) +#define FPC_EVT_INIT_RESULT (0x02) +#define FPC_EVT_FINGER_DWN (0x06) +#define FPC_EVT_IMG (0x08) +#define FPC_EVT_FID_DATA (0x31) +#define FPC_DB_ID_LEN (16) +#define FPC_IDENTITY_TYPE_WILDCARD (0x1) +#define FPC_IDENTITY_TYPE_RESERVED (0x3) +#define FPC_IDENTITY_WILDCARD (0x25066282) +#define FPC_SUBTYPE_ANY (0xFF) +#define FPC_SUBTYPE_RESERVED (0xF5) +#define FPC_SUBTYPE_NOINFORMATION (0x00) +#define FPC_CAPTUREID_RESERVED (0x701100F) +#define FPC_SESSIONID_RESERVED (0x0077FF12) +#define FPC_TEMPLATES_MAX (10) +#define SECURITY_MAX_SID_SIZE (68) +#define FPC_HOST_MS_S0 (0x10) +#define FPC_HOST_MS_SX (0x11) + +G_DECLARE_FINAL_TYPE (FpiDeviceFpcMoc, fpi_device_fpcmoc, FPI, + DEVICE_FPCMOC, FpDevice); + +typedef struct _FPC_FID_DATA +{ + guint32 identity_type; + guint32 reserved; + guint32 identity_size; + guint32 subfactor; + guint8 data[SECURITY_MAX_SID_SIZE]; +} FPC_FID_DATA, *PFPC_FID_DATA; + +typedef struct _FPC_LOAD_DB +{ + gint32 status; + guint32 reserved; + guint32 database_id_size; + guint8 data[FPC_DB_ID_LEN]; +} FPC_LOAD_DB, *PFPC_LOAD_DB; + +typedef union _FPC_DELETE_DB +{ + guint32 reserved; + guint32 database_id_size; + guint8 data[FPC_DB_ID_LEN]; +} FPC_DB_OP, *PFPC_DB_OP; + +typedef struct _FPC_BEGIN_ENROL +{ + gint32 status; + guint32 reserved1; + guint32 reserved2; +} FPC_BEGIN_ENROL, *PFPC_BEGIN_ENROL; + +typedef struct _FPC_ENROL +{ + gint32 status; + guint32 remaining; +} FPC_ENROL, *PFPC_ENROL; + +typedef struct _FPC_END_ENROL +{ + gint32 status; + guint32 fid; +} FPC_END_ENROL, *PFPC_END_ENROL; + +typedef struct _FPC_IDENTIFY +{ + gint32 status; + guint32 identity_type; + guint32 identity_offset; + guint32 identity_size; + guint32 subfactor; + guint8 data[SECURITY_MAX_SID_SIZE]; +} FPC_IDENTIFY, *PFPC_IDENTIFY; + +typedef struct +{ + guint32 cmdid; + guint32 length; + guint32 status; +} evt_hdr_t; + +typedef struct +{ + evt_hdr_t hdr; + guint16 sensor; + guint16 hw_id; + guint16 img_w; + guint16 img_h; + guint8 fw_version[MAX_FW_VERSION_STR_LEN]; + guint16 fw_capabilities; +} evt_initiated_t; + +typedef struct +{ + guint8 subfactor; + guint32 identity_type; + guint32 identity_size; + guint8 identity[SECURITY_MAX_SID_SIZE]; +} __attribute__((packed)) fpc_fid_data_t; + +typedef struct +{ + evt_hdr_t hdr; + gint status; + guint32 num_ids; + fpc_fid_data_t fid_data[FPC_TEMPLATES_MAX]; +} __attribute__((packed)) evt_enum_fids_t; + +typedef struct _fp_cmd_response +{ + union + { + evt_hdr_t evt_hdr; + evt_initiated_t evt_inited; + evt_enum_fids_t evt_enum_fids; + }; +} fpc_cmd_response_t, *pfpc_cmd_response_t; + +enum { + FPC_ENROL_STATUS_COMPLETED = 0, + FPC_ENROL_STATUS_PROGRESS = 1, + FPC_ENROL_STATUS_FAILED_COULD_NOT_COMPLETE = 2, + FPC_ENROL_STATUS_FAILED_ALREADY_ENROLED = 3, + FPC_ENROL_STATUS_IMAGE_LOW_COVERAGE = 4, + FPC_ENROL_STATUS_IMAGE_TOO_SIMILAR = 5, + FPC_ENROL_STATUS_IMAGE_LOW_QUALITY = 6, +}; + +typedef enum { + FPC_CMDTYPE_UNKNOWN = 0, + FPC_CMDTYPE_TO_DEVICE, + FPC_CMDTYPE_TO_DEVICE_EVTDATA, + FPC_CMDTYPE_FROM_DEVICE, +} FpcCmdType; + +typedef enum { + FP_CMD_SEND = 0, + FP_CMD_GET_DATA, + FP_CMD_SUSPENDED, + FP_CMD_RESUME, + FP_CMD_NUM_STATES, +} FpCmdState; + +typedef enum { + FP_INIT = 0, + FP_LOAD_DB, + FP_INIT_NUM_STATES, +} FpInitState; + +typedef enum { + FP_ENROLL_ENUM = 0, + FP_ENROLL_CREATE, + FP_ENROLL_CAPTURE, + FP_ENROLL_GET_IMG, + FP_ENROLL_UPDATE, + FP_ENROLL_COMPLETE, + FP_ENROLL_CHECK_DUPLICATE, + FP_ENROLL_BINDID, + FP_ENROLL_COMMIT, + FP_ENROLL_DICARD, + FP_ENROLL_CLEANUP, + FP_ENROLL_NUM_STATES, +} FpEnrollState; + +typedef enum { + FP_VERIFY_CAPTURE = 0, + FP_VERIFY_GET_IMG, + FP_VERIFY_IDENTIFY, + FP_VERIFY_CANCEL, + FP_VERIFY_NUM_STATES, +} FpVerifyState; + +typedef enum { + FP_CLEAR_DELETE_DB = 0, + FP_CLEAR_CREATE_DB, + FP_CLEAR_NUM_STATES, +} FpClearState; diff --git a/libfprint/meson.build b/libfprint/meson.build index 1a62a5f4..d3c8b034 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -139,6 +139,8 @@ driver_sources = { [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ], 'goodixmoc' : [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], + 'fpcmoc' : + [ 'drivers/fpcmoc/fpc.c' ], } helper_sources = { diff --git a/meson.build b/meson.build index 823728c7..e25a1736 100644 --- a/meson.build +++ b/meson.build @@ -125,6 +125,7 @@ default_drivers = [ 'upekts', 'goodixmoc', 'nb1010', + 'fpcmoc', # SPI 'elanspi', diff --git a/tests/fpcmoc/custom.pcapng b/tests/fpcmoc/custom.pcapng new file mode 100644 index 0000000000000000000000000000000000000000..57b6f7668cf327257d70a8fdeaf7a8a62b2056d0 GIT binary patch literal 25324 zcmeHP4RBOdmVP885QKot6yTuJ62$~a>?DK;qQ2cQLNKfdL}8X;UP6#W{$jeph|E;0 zBe<9ZP#9Q;Wff(XErCV(7pCmR8GmLKot;rBKy((CD&=RKc~ zcg@z;dZ^R=er|u~o_o%B?tM2+ck9uk+kV3^#*Z5Ey-xIgNlJz>+^C!vo;fFfVnLu{ ze$&kS$>R$G4dsQy%Ywn`K*faWfEg$$DhrMoyC{(VU_-b*Flo~FhZU6+7)GYi*Qjck zx1c#tR#aSC6f7(&DV#NP-pru5E(nws4J#@S zicIZZ=)-ewn_|ZTs`w&f*i24JWdUbwhGPCLOaz*L`#Hk1H=-nv@aw4%r)Z-L_-3 zblI5pTsNbY#;P};kVLY%J2;~Y8TMrxm+`~m)W$zW_!H@z75Ei4exU*U7YUz*5o1v8 zYxax8@7>9^WNz&B$)#RKrwr}|+tPhHpMXz0q)$203i(7JM z&f1<&RU3p)j2MF|QRq$L=VV-Ebn4Xk*%G?MHgsS1ku8(1v%;1hpNGj7$LA_)!yQy>XP+HhH1BBo4Rk; z-7rA;W|V#!mwN+c?mai9yYJj1(2M(?63I3j~pMF93Ea|QfuQz)6Ch7ymGrLy!W_N&JlBf^C2i*9m55R9E z{6xf}Mx1ZVF&29*)Rd?Ai?N$z*B`?G{?Qqt|KP9LiS4nTA@A}S9U}bu=$sY! z6#`$4QNQ~{{|8YMR+mV8e#8B9Sy%S8Xa!kZR8n3PEGp)jz`||Vem1E^%EJ9vzQDsz zxo(A@?@~Xw4qh!jW<)JMz!@ECe#Y`Bb75X+8Z}{6e#&(#@GE8sKN*qLB@&;nbH(gM z`j3z7d}PZvCo7(P?}-E7i9GeiV!Fn@HIlNhZ{wTuLKEni6}I&}Y;6#}F(#{7%Z>Rc zUUYknbXy+fg>eN2z>yVPfsuCt=gdW?HEi!tH;UQR5!spBtP>flHBo)T+$t?uk1eCDH( zVBKk^l^2W6iDh}Ad+3}M_4rt5X-o*2k-K zxiel-X+y8GA+o{ZMzt@i7mJQ`{MJnle&i+ZI{VJwCRb;9tXO!Ae$=By#RK@Lvjy^* zYrq4!cKeRv!F@-(5D&}#q_8zLw?*Yvbbow2VC$-;Trp?J&zYW^X<}`tkJW1)_=pAY zA0zxk#G*zzeyFF@e`9-mEP!7clKoF~&Xb+tScC_)gr?9rEAaJL3|l7p-;bKGx$j#+^Z z&JUJ3cJ?m85k_QniNxntSCH0|vLDPz7Pd1W*ruN6BEN(mg72{N_jjkp_aBoDlkBm= z267BG{)=p|_~CnLu^WGqTvq{q`$YeIe{;ZEhwpNXkYm6f`xiXIg3I8ueE}~r7CipA z@5T4+fwgL^u!h6B8MaFQ@(SO4zxp~iz8;HpgrA65)JW&Wz8HLSXFT80|63|#|2>nI zKs8=4|5!L*Un*a#_zrwM7R6IV|C!6+vkib3iO=S7zaze{z1bPJ$PVyC@Ev~j`+@Ka zZY9I_Idh*;dzt#bo&Y;R_obzDs=Y9r%5JDi(5y@SR(F@qOGcHY&bH&chGEci36>cuq=u z-#|8aoLFH)&#`|a8!UeK-uoXM{OGy({aW}iaf$H358sch*zE9K;v>g~O1>W{6zh{z z`F?TbW;Ip@_JHL6Ic!}ll4})v#AsZTSkKoy<;F)WfM0Wy@I}J6`)1J{o$oh39fPmc zUf|ytOm3~!EPKFwpT1?Y;=8_{2mbVNqW|gSd(~FQFXuT+%)Z~@2_B;BKQtF^~*9-;`^&)!zBBx5otruu~*3kiyyu( zI_t*AoB|&fJt=(f!}qnlb}PQC{>#750DtY4^sMKXUAbG072=Nh9k!Nj^}n7!?}4w! z;ymFeA{I5$`M$2V(|_<$kHxxw^1q(nkiWY_zHj(-`q%TT3u63IbvT~yt6%rOo*&$_ zSMeQnUg!JZo?>4j5x(;Z$BXalLwg;*W3Nk_PuBMm`w+}U)@0Z7=g5Z1pCeWKUGh68 z$l-HjgT)WuUk*F?*x%H1?By$y+aroy&tE(mReT3N=J!H5zwavN%uoIK{Xaj7s|L_K;)m}a#^6VG$L|rrhczz=ANn9t9Uw-)h`mw``@4yG&Z<0Bdy;D4=bi{XV#c`>Z?{)Of!p}oDe{xvy9eZiWFW70` zC7w%|i>%4c?`>qm_3R1TgVU>fFH~E!JCgOz5`#6#r=QN4nJ->=6wI! z58uahJ*jfokT&!jTS_)q{P4X#$BnP&*rt!=ci=q-iVSNx7R&d~2Ax%WSN)gI??G96 zKYN8oICZ{P=bu$$g}8(7uvNW3%QxTGc;F)zz+Xf7iHJpwbiPk4aQYv)RpyhN-+@2z zm$Lr}#v+#QpWJp<@g4Z!JMcgGSJD4;^8Fgq@yjd&PsDm2cCY!p@C$Aw!}q@HKUaJQ zKKKs&z9j;gUtuFIPBMqtd)9hVj!zva3(xPl&wlRkJqSMp-(e?rSZeF}g=E7dd#r}E zq375_vcckq@3%bX;77{6bL^Hf|LggEr@mBt2R?GFSn~bhTgCc>tCpx-wmZ&?j0KNB z?sHAi`Eud(mujpcBje)%TRU%)YZZx}%>Mk%XWaOR1@ONl{6xf}Mtc9hiowVKuR-kv z{@&Y@TWiJgeO2~XitoVJV=;M(=zlu--oNWtDlV{#^*sDKGFA8mE+)hG*1E41-+>Rl z1AqLk#P{c!M_zpY>-)Y|d~Ywm`tM0?fBpd3Fv&hEZ0I@mDA{1~!}qPTV(>M-W31-w z^}j#={?3eW74?m^J-)B*lAitfzuKJ!CBe}Dcd4}3iq6A3>Nv8a*G z_gQ+J|NHZs-pUA1A-k-=*ZE%GKmGgj#=kgzVO^o~J$kkO{rQ(iW`)O+ zPprV#`QFr$`1fs?OqJ zzdOYEmlKO_>N$4sRtF#JZ9T`@mizy{&FC@R72ko694nFY`>*a1Ye=5ESzVIg{JwN- zcQsb91-`>pmnr_&^KW_JBNo7~B>Y6gqDFcw9;z}I83 zu1WMio%8#&I>#^Y5qyVVM??PC^Lr2Hw##?mkN8Y{kEkn+JF81%j`8Y-554&Q;?Z2i zchpNgzlNL^-y8DF_Z}bRsvL$5J;z$f28$oQ_dDj`t2t3VGh(dPo=NUIU32W-h-aQn z*Y$Duj^`2i{0{v5;-2;=Qsw)(fqm3iVJ)G@BUIAU_w)PkJU6}`i(Q1Dh*;D}=lkce z_;?=CV$oPC`|p{w1S<0u_|0^Qg`YovGqR83yIL#CSd_}z+fCx8lkYjBoN>c?8+?b| zO8VXt&x>#?nf3h74)jxe2R`O^;D4}O3=HGLhCMKNk>keIu06iD{i>hhJM198VCUe< zjFjg0^JK%MKCr?DattK=B>;dMrM?D*gNOhsHbOhB-^;d%ynv_vh!H8mRaVe4X!G_K1~ZM||h`owZ)Z zr;grPxLy{X8L0T~tW(5(9pW?hmDKjp?6Jazo@4Kk4HiFqZ#?VZV~wZtecL|& z`}3PBixl61j~p8&=afyq7Zo?<`F&iG8Y_K04_oKnmwS))h|#zv@qOCzJKgw*1@Oy1 z@SHuQHRsKuL!Iwmc;F)z!2g2q{eIu3xu(eBJO0)q=Xc;YpBDX3=lq^E!x=Z^0Qe5O zS!ab`7`pB(X2S_^{zbgO22!g=_V6pvPzYZ-Oenz(w7U@zvTs zL<1wnSk&f5w!^~pr1ovc58&&5)ZUxAAN${N{6Kxw{n$T6_%TIv&xp?E1Dw&J=0{kz zV?Pf3E*R$DL|_HJ?#F@Y!jJD#8&;P{{D)4fpJh@_!gR#3*&(m6&D-A(hF8%!D}I*^ zYcJ%$?UwLqGPPkf@23}!-iFY6bnp%Bx34fJ<7&m2VD5<4uoaxqp%&-Z`S0}5 z5o-PeKH^*^Yy0K}0*hZ`P}ztOiO(mDt^07MaD?KV{u@KEv1MWB_-D3Ep0V9x(Ip*! zc##_)F#!HW!cRni&#`9Vyu1_~q4F}a2ERWh)(F6V^&!!Jjxp>1_HUo`DRIUGeb&e1 zgGFLY{N!cVQU~8$EyqMY$6`$0d|1XbrgZX{D$(*X_P0;ASB(hYLnLMeKGqz-53Lsc z=ifD9b&13;rW>*MIkPTsdoB*?C=3Mit$aHI|JOzUzkR|rn}us# zo`+Qbk(}?v#bp zGXl;(@JeVSdH> z8+MP?3cs-8?r8nJN)9FSu=tvqa1))g0w3!#;6LyO@%PLUYQyT1n6UA5_g&IN*TdWw zJ(gczTcdJOU&o*DzW z{Xh3=fsfh-{L)kY|8^T`S)^*4>c6aQBV=te z-xdqHRBPM9)0S|oS6Yw7;{6xf}MmisswK@F z7OC0>eAG7JfB%H&e>!X1rj3qYs%FdIN`T$^e;0oFscj{BOH^$GK5E-YnPV>p#NVX< zT(xcc^-EMP;&}tL0evbRsC|zhiQ4vs2R?EU_zx0(B7$7pAscgTJ33^EdPc>XMDPE! t0@44;^f^{@1_SU>yzpG{=h!`wRIrIWX51IChlIw_t~P;{{bc<-gW>0 literal 0 HcmV?d00001 diff --git a/tests/fpcmoc/custom.py b/tests/fpcmoc/custom.py new file mode 100755 index 00000000..c99dbd03 --- /dev/null +++ b/tests/fpcmoc/custom.py @@ -0,0 +1,86 @@ +#!/usr/bin/python3 + +import gi +gi.require_version('FPrint', '2.0') +from gi.repository import FPrint, GLib + +ctx = GLib.main_context_default() + +c = FPrint.Context() +c.enumerate() +devices = c.get_devices() + +d = devices[0] +del devices + +assert d.get_driver() == "fpcmoc" +assert not d.has_feature(FPrint.DeviceFeature.CAPTURE) +assert d.has_feature(FPrint.DeviceFeature.IDENTIFY) +assert d.has_feature(FPrint.DeviceFeature.VERIFY) +assert d.has_feature(FPrint.DeviceFeature.DUPLICATES_CHECK) +assert d.has_feature(FPrint.DeviceFeature.STORAGE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE) +assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR) + +d.open_sync() + +print("Clear") +d.clear_storage_sync() +print("Clear done") + +template = FPrint.Print.new(d) + +def enroll_progress(*args): + assert d.get_finger_status() == FPrint.FingerStatusFlags.NEEDED + print('enroll progress: ' + str(args)) + +def identify_done(dev, res): + global identified + identified = True + identify_match, identify_print = dev.identify_finish(res) + print('indentification_done: ', identify_match, identify_print) + assert identify_match.equal(identify_print) + +# List, enroll, list, verify, identify, delete +print("enrolling") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +p = d.enroll_sync(template, None, enroll_progress, None) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("enroll done") + +print("listing") +stored = d.list_prints_sync() +print("listing done") +assert len(stored) == 1 +assert stored[0].equal(p) +print("verifying") +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +verify_res, verify_print = d.verify_sync(p) +assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE +print("verify done") +del p +assert verify_res == True + +identified = False +deserialized_prints = [] +for p in stored: + deserialized_prints.append(FPrint.Print.deserialize(p.serialize())) + assert deserialized_prints[-1].equal(p) +del stored + +print('async identifying') +d.identify(deserialized_prints, callback=identify_done) +del deserialized_prints + +while not identified: + ctx.iteration(True) + +print("deleting") +d.delete_print_sync(p) +print("delete done") + +d.close_sync() + +del d +del c diff --git a/tests/fpcmoc/device b/tests/fpcmoc/device new file mode 100644 index 00000000..32194633 --- /dev/null +++ b/tests/fpcmoc/device @@ -0,0 +1,234 @@ +P: /devices/pci0000:00/0000:00:14.0/usb1/1-1 +N: bus/usb/001/019=1201000200000040A510E0FF10000102000109021900010104A0320904000001FFFFFF0507058102400000 +E: DEVNAME=/dev/bus/usb/001/019 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=10a5/ffe0/10 +E: TYPE=0/0/0 +E: BUSNUM=001 +E: DEVNUM=019 +E: MAJOR=189 +E: MINOR=18 +E: SUBSYSTEM=usb +E: ID_VENDOR=FPC +E: ID_VENDOR_ENC=FPC +E: ID_VENDOR_ID=10a5 +E: ID_MODEL=FPC_L:0001_FW:127010 +E: ID_MODEL_ENC=FPC\x20L:0001\x20FW:127010 +E: ID_MODEL_ID=ffe0 +E: ID_REVISION=0010 +E: ID_SERIAL=FPC_FPC_L:0001_FW:127010 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:ffffff: +E: ID_PATH=pci-0000:00:14.0-usb-0:1 +E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_1 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0-usb-0_1 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +A: authorized=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=00\n +A: bDeviceProtocol=00\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=100mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0010\n +A: bmAttributes=a0\n +A: busnum=1\n +A: configuration=FPC\n +H: descriptors=1201000200000040A510E0FF10000102000109021900010104A0320904000001FFFFFF0507058102400000 +A: dev=189:18\n +A: devnum=19\n +A: devpath=1\n +L: driver=../../../../../bus/usb/drivers/usb +L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1c/device:1d/device:1e +A: idProduct=ffe0\n +A: idVendor=10a5\n +A: ltm_capable=no\n +A: manufacturer=FPC\n +A: maxchild=0\n +L: port=../1-0:1.0/usb1-port1 +A: power/active_duration=27204308\n +A: power/async=enabled\n +A: power/autosuspend=2\n +A: power/autosuspend_delay_ms=2000\n +A: power/connected_duration=27204308\n +A: power/control=on\n +A: power/level=on\n +A: power/persist=1\n +A: power/runtime_active_kids=0\n +A: power/runtime_active_time=27204033\n +A: power/runtime_enabled=forbidden\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=1\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=FPC L:0001 FW:127010\n +A: quirks=0x0\n +A: removable=removable\n +A: rx_lanes=1\n +A: speed=12\n +A: tx_lanes=1\n +A: urbnum=31\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0/usb1 +N: bus/usb/001/001=12010002090001406B1D020013050302010109021900010100E0000904000001090000000705810304000C +E: DEVNAME=/dev/bus/usb/001/001 +E: DEVTYPE=usb_device +E: DRIVER=usb +E: PRODUCT=1d6b/2/513 +E: TYPE=9/0/1 +E: BUSNUM=001 +E: DEVNUM=001 +E: MAJOR=189 +E: MINOR=0 +E: SUBSYSTEM=usb +E: ID_VENDOR=Linux_5.13.0-52-generic_xhci-hcd +E: ID_VENDOR_ENC=Linux\x205.13.0-52-generic\x20xhci-hcd +E: ID_VENDOR_ID=1d6b +E: ID_MODEL=xHCI_Host_Controller +E: ID_MODEL_ENC=xHCI\x20Host\x20Controller +E: ID_MODEL_ID=0002 +E: ID_REVISION=0513 +E: ID_SERIAL=Linux_5.13.0-52-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0 +E: ID_SERIAL_SHORT=0000:00:14.0 +E: ID_BUS=usb +E: ID_USB_INTERFACES=:090000: +E: ID_VENDOR_FROM_DATABASE=Linux Foundation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=2.0 root hub +E: ID_PATH=pci-0000:00:14.0 +E: ID_PATH_TAG=pci-0000_00_14_0 +E: ID_FOR_SEAT=usb-pci-0000_00_14_0 +E: TAGS=:seat: +E: CURRENT_TAGS=:seat: +A: authorized=1\n +A: authorized_default=1\n +A: avoid_reset_quirk=0\n +A: bConfigurationValue=1\n +A: bDeviceClass=09\n +A: bDeviceProtocol=01\n +A: bDeviceSubClass=00\n +A: bMaxPacketSize0=64\n +A: bMaxPower=0mA\n +A: bNumConfigurations=1\n +A: bNumInterfaces= 1\n +A: bcdDevice=0513\n +A: bmAttributes=e0\n +A: busnum=1\n +A: configuration= +H: descriptors=12010002090001406B1D020013050302010109021900010100E0000904000001090000000705810304000C +A: dev=189:0\n +A: devnum=1\n +A: devpath=0\n +L: driver=../../../../bus/usb/drivers/usb +L: firmware_node=../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1c/device:1d +A: idProduct=0002\n +A: idVendor=1d6b\n +A: interface_authorized_default=1\n +A: ltm_capable=no\n +A: manufacturer=Linux 5.13.0-52-generic xhci-hcd\n +A: maxchild=12\n +A: power/active_duration=32728176\n +A: power/async=enabled\n +A: power/autosuspend=0\n +A: power/autosuspend_delay_ms=0\n +A: power/connected_duration=32728176\n +A: power/control=auto\n +A: power/level=auto\n +A: power/runtime_active_kids=2\n +A: power/runtime_active_time=32728177\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=disabled\n +A: power/wakeup_abort_count=\n +A: power/wakeup_active=\n +A: power/wakeup_active_count=\n +A: power/wakeup_count=\n +A: power/wakeup_expire_count=\n +A: power/wakeup_last_time_ms=\n +A: power/wakeup_max_time_ms=\n +A: power/wakeup_total_time_ms=\n +A: product=xHCI Host Controller\n +A: quirks=0x0\n +A: removable=unknown\n +A: rx_lanes=1\n +A: serial=0000:00:14.0\n +A: speed=480\n +A: tx_lanes=1\n +A: urbnum=1353\n +A: version= 2.00\n + +P: /devices/pci0000:00/0000:00:14.0 +E: DRIVER=xhci_hcd +E: PCI_CLASS=C0330 +E: PCI_ID=8086:9D2F +E: PCI_SUBSYS_ID=103C:8079 +E: PCI_SLOT_NAME=0000:00:14.0 +E: MODALIAS=pci:v00008086d00009D2Fsv0000103Csd00008079bc0Csc03i30 +E: SUBSYSTEM=pci +E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller +E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller +E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI +E: ID_VENDOR_FROM_DATABASE=Intel Corporation +E: ID_AUTOSUSPEND=1 +E: ID_MODEL_FROM_DATABASE=Sunrise Point-LP USB 3.0 xHCI Controller (EliteBook 840 G3) +A: ari_enabled=0\n +A: broken_parity_status=0\n +A: class=0x0c0330\n +H: config=86802F9D060490022130030C00008000040022E10000000000000000000000000000000000000000000000003C1079800000000070000000000000000B010000FD01348088C60F8000000000000000005F6ECE0F000000000000000000000000306000000000000000000000000000000180C2C1080000000000000000000000050087000480E0FE0000000022000000090014F01000400100000000C10A080000080400001800008F40020000010400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B30F400800000000 +A: consistent_dma_mask_bits=64\n +A: d3cold_allowed=1\n +A: dbc=disabled\n +A: device=0x9d2f\n +A: dma_mask_bits=64\n +L: driver=../../../bus/pci/drivers/xhci_hcd +A: driver_override=(null)\n +A: enable=1\n +L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1c +A: irq=122\n +A: local_cpulist=0-3\n +A: local_cpus=f\n +A: modalias=pci:v00008086d00009D2Fsv0000103Csd00008079bc0Csc03i30\n +A: msi_bus=1\n +A: msi_irqs/122=msi\n +A: numa_node=-1\n +A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 32 128 1\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 11 14 2112 14\nxHCI ring segments 34 44 4096 44\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 32 128 1\nbuffer-32 0 0 32 0\n +A: power/async=enabled\n +A: power/control=auto\n +A: power/runtime_active_kids=1\n +A: power/runtime_active_time=32728973\n +A: power/runtime_enabled=enabled\n +A: power/runtime_status=active\n +A: power/runtime_suspended_time=0\n +A: power/runtime_usage=0\n +A: power/wakeup=enabled\n +A: power/wakeup_abort_count=0\n +A: power/wakeup_active=0\n +A: power/wakeup_active_count=0\n +A: power/wakeup_count=0\n +A: power/wakeup_expire_count=0\n +A: power/wakeup_last_time_ms=0\n +A: power/wakeup_max_time_ms=0\n +A: power/wakeup_total_time_ms=0\n +A: power_state=D0\n +A: resource=0x00000000e1220000 0x00000000e122ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n +A: revision=0x21\n +A: subsystem_device=0x8079\n +A: subsystem_vendor=0x103c\n +A: vendor=0x8086\n + diff --git a/tests/meson.build b/tests/meson.build index 96db6260..94d47100 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -39,6 +39,7 @@ drivers_tests = [ 'goodixmoc', 'nb1010', 'egis0570', + 'fpcmoc', ] if get_option('introspection')