mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2025-11-15 07:38:12 +00:00
1444 lines
49 KiB
C
1444 lines
49 KiB
C
/*
|
|
* Driver for Egis Technology (LighTuning) Match-On-Chip sensors
|
|
* Originally authored 2023 by Joshua Grisham <josh@joshuagrisham.com>
|
|
*
|
|
* Portions of code and logic inspired from the elanmoc libfprint driver
|
|
* which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c)
|
|
*
|
|
* Based on original reverse-engineering work by Joshua Grisham. The protocol has
|
|
* been reverse-engineered from captures of the official Windows driver, and by
|
|
* testing commands on the sensor with a multiplatform Python prototype driver:
|
|
* https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/
|
|
*
|
|
* 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 "egismoc"
|
|
|
|
#include <stdio.h>
|
|
#include <glib.h>
|
|
#include <sys/param.h>
|
|
|
|
#include "drivers_api.h"
|
|
|
|
#include "egismoc.h"
|
|
|
|
G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE);
|
|
|
|
static const FpIdEntry egismoc_id_table[] = {
|
|
{ .vid = 0x1c7a, .pid = 0x0582 },
|
|
{ .vid = 0, .pid = 0 }
|
|
};
|
|
|
|
typedef void (*SynCmdMsgCallback) (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error);
|
|
|
|
typedef struct egismoc_command_data
|
|
{
|
|
SynCmdMsgCallback callback;
|
|
} CommandData;
|
|
|
|
typedef struct egismoc_enroll_print
|
|
{
|
|
FpPrint *print;
|
|
int stage;
|
|
} EnrollPrint;
|
|
|
|
static void
|
|
egismoc_finger_on_sensor_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *device,
|
|
gpointer userdata,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Finger on sensor callback");
|
|
fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
|
|
|
|
g_return_if_fail (transfer->ssm);
|
|
if (error)
|
|
fpi_ssm_mark_failed (transfer->ssm, error);
|
|
else
|
|
fpi_ssm_next_state (transfer->ssm);
|
|
}
|
|
|
|
static void
|
|
egismoc_wait_finger_on_sensor (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
fp_dbg ("Wait for finger on sensor");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device);
|
|
|
|
fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN, EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH);
|
|
transfer->ssm = ssm;
|
|
transfer->short_is_error = FALSE; /* Interrupt on this device always returns 1 byte short; this is expected */
|
|
|
|
fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
|
|
|
|
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
|
|
EGISMOC_USB_INTERRUPT_TIMEOUT,
|
|
self->interrupt_cancellable,
|
|
egismoc_finger_on_sensor_cb,
|
|
NULL);
|
|
}
|
|
|
|
static gboolean
|
|
egismoc_validate_response_prefix (const guchar *buffer_in,
|
|
const gsize buffer_in_len,
|
|
const guchar *valid_prefix,
|
|
const gsize valid_prefix_len)
|
|
{
|
|
const gboolean result = memcmp (buffer_in + (egismoc_read_prefix_len + EGISMOC_CHECK_BYTES_LENGTH),
|
|
valid_prefix,
|
|
valid_prefix_len) == 0;
|
|
|
|
fp_dbg ("Response prefix valid: %s", result ? "yes" : "NO");
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
egismoc_validate_response_suffix (const guchar *buffer_in,
|
|
const gsize buffer_in_len,
|
|
const guchar *valid_suffix,
|
|
const gsize valid_suffix_len)
|
|
{
|
|
const gboolean result = memcmp (buffer_in + (buffer_in_len - valid_suffix_len),
|
|
valid_suffix,
|
|
valid_suffix_len) == 0;
|
|
|
|
fp_dbg ("Response suffix valid: %s", result ? "yes" : "NO");
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
egismoc_task_ssm_done (FpiSsm *ssm,
|
|
FpDevice *device,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Task SSM done");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
/* task_ssm already freed by completion of SSM */
|
|
self->task_ssm = NULL;
|
|
|
|
if (self->enrolled_ids)
|
|
g_ptr_array_free (self->enrolled_ids, TRUE);
|
|
self->enrolled_ids = NULL;
|
|
self->enrolled_num = -1;
|
|
|
|
if (error)
|
|
fpi_device_action_error (device, error);
|
|
}
|
|
|
|
static void
|
|
egismoc_task_ssm_next_state_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Task SSM next state callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
if (error)
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
else
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
|
|
static void
|
|
egismoc_cmd_receive_cb (FpiUsbTransfer *transfer,
|
|
FpDevice *device,
|
|
gpointer userdata,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Command receive callback");
|
|
CommandData *data = userdata;
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm, error);
|
|
return;
|
|
}
|
|
if (data == NULL || transfer->actual_length < egismoc_read_prefix_len)
|
|
{
|
|
fpi_ssm_mark_failed (transfer->ssm,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
|
|
return;
|
|
}
|
|
|
|
if (data->callback)
|
|
data->callback (device, transfer->buffer, transfer->actual_length, NULL);
|
|
|
|
fpi_ssm_mark_completed (transfer->ssm);
|
|
}
|
|
|
|
static void
|
|
egismoc_cmd_run_state (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
g_autoptr(FpiUsbTransfer) transfer = NULL;
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case CMD_SEND:
|
|
if (self->cmd_transfer)
|
|
{
|
|
self->cmd_transfer->ssm = ssm;
|
|
fpi_usb_transfer_submit (g_steal_pointer (&self->cmd_transfer),
|
|
EGISMOC_USB_SEND_TIMEOUT,
|
|
NULL,
|
|
fpi_ssm_usb_transfer_cb,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_next_state (ssm);
|
|
}
|
|
break;
|
|
|
|
case CMD_GET:
|
|
transfer = fpi_usb_transfer_new (device);
|
|
transfer->ssm = ssm;
|
|
fpi_usb_transfer_fill_bulk (transfer, EGISMOC_EP_CMD_IN, EGISMOC_USB_IN_RECV_LENGTH);
|
|
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
|
|
EGISMOC_USB_RECV_TIMEOUT,
|
|
NULL,
|
|
egismoc_cmd_receive_cb,
|
|
fpi_ssm_get_data (ssm));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_cmd_ssm_done (FpiSsm *ssm,
|
|
FpDevice *device,
|
|
GError *error)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
CommandData *data = fpi_ssm_get_data (ssm);
|
|
|
|
self->cmd_ssm = NULL;
|
|
self->cmd_transfer = NULL;
|
|
|
|
if (error)
|
|
{
|
|
if (data->callback)
|
|
data->callback (device, NULL, 0, error);
|
|
else
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
typedef union egismoc_check_bytes
|
|
{
|
|
unsigned short check_short;
|
|
guchar check_bytes[EGISMOC_CHECK_BYTES_LENGTH];
|
|
} EgisMocCheckBytes;
|
|
|
|
/*
|
|
Derive the 2 "check bytes" for write payloads
|
|
32-bit big-endian sum of all 16-bit words (including check bytes) MOD 0xFFFF should be 0, otherwise
|
|
the device will reject the payload
|
|
*/
|
|
static EgisMocCheckBytes
|
|
egismoc_get_check_bytes (const guchar *value,
|
|
const gsize value_length)
|
|
{
|
|
fp_dbg ("Get check bytes");
|
|
EgisMocCheckBytes check_bytes;
|
|
unsigned short value_bigendian_shorts[(int) ((value_length + 1) / 2)];
|
|
int s = 0;
|
|
|
|
for (int i = 0; i < value_length; i = i + 2, s++)
|
|
{
|
|
if (i + 1 < value_length)
|
|
value_bigendian_shorts[s] = (((short) value[i + 1]) << 8) | (0x00ff & value[i]);
|
|
else
|
|
value_bigendian_shorts[s] = (((short) 0x00) << 8) | (0x00ff & value[i]);
|
|
}
|
|
unsigned long sum_shorts = 0;
|
|
|
|
for (int i = 0; i < s; i++)
|
|
sum_shorts += value_bigendian_shorts[i];
|
|
|
|
/*
|
|
derive the "first possible occurence" of check bytes as:
|
|
`0xFFFF - (sum_of_32bit_words % 0xFFFF)
|
|
*/
|
|
check_bytes.check_short = 0xffff - (sum_shorts % 0xffff);
|
|
return check_bytes;
|
|
}
|
|
|
|
static void
|
|
egismoc_exec_cmd (FpDevice *device,
|
|
guchar *cmd,
|
|
const gsize cmd_length,
|
|
GDestroyNotify cmd_destroy,
|
|
SynCmdMsgCallback callback)
|
|
{
|
|
fp_dbg ("Execute command and get response");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
EgisMocCheckBytes check_bytes;
|
|
g_autofree guchar *buffer_out = NULL;
|
|
gsize buffer_out_length = 0;
|
|
|
|
g_autoptr(FpiUsbTransfer) transfer = NULL;
|
|
CommandData *data = g_new0 (CommandData, 1);
|
|
|
|
self->cmd_ssm = fpi_ssm_new (device,
|
|
egismoc_cmd_run_state,
|
|
CMD_STATES);
|
|
|
|
transfer = fpi_usb_transfer_new (device);
|
|
transfer->short_is_error = TRUE;
|
|
|
|
/*
|
|
buffer_out should be a fully composed command (with prefix, check bytes, etc) which looks like this
|
|
E G I S 00 00 00 01 {cb1} {cb2} {payload}
|
|
where cb1 and cb2 are some check bytes generated by the egismoc_get_check_bytes method
|
|
and payload is what is passed via the cmd parameter
|
|
*/
|
|
buffer_out_length = egismoc_write_prefix_len
|
|
+ EGISMOC_CHECK_BYTES_LENGTH
|
|
+ cmd_length;
|
|
buffer_out = g_malloc0 (buffer_out_length);
|
|
|
|
/* Prefix */
|
|
memcpy (buffer_out, egismoc_write_prefix, egismoc_write_prefix_len);
|
|
|
|
/* Check Bytes - leave them as 00 for now then later generate and copy over the real ones */
|
|
|
|
/* Command Payload */
|
|
memcpy (buffer_out + egismoc_write_prefix_len + EGISMOC_CHECK_BYTES_LENGTH, cmd, cmd_length);
|
|
|
|
/* destroy cmd if requested */
|
|
if (cmd_destroy)
|
|
cmd_destroy (cmd);
|
|
|
|
/* Now fetch and set the "real" check bytes based on the currently assembled payload */
|
|
check_bytes = egismoc_get_check_bytes (buffer_out, buffer_out_length);
|
|
memcpy (buffer_out + egismoc_write_prefix_len, check_bytes.check_bytes, EGISMOC_CHECK_BYTES_LENGTH);
|
|
|
|
fpi_usb_transfer_fill_bulk_full (transfer,
|
|
EGISMOC_EP_CMD_OUT,
|
|
g_steal_pointer (&buffer_out),
|
|
buffer_out_length,
|
|
g_free);
|
|
transfer->ssm = self->cmd_ssm;
|
|
self->cmd_transfer = g_steal_pointer (&transfer);
|
|
data->callback = callback;
|
|
|
|
fpi_ssm_set_data (self->cmd_ssm, data, g_free);
|
|
fpi_ssm_start (self->cmd_ssm, egismoc_cmd_ssm_done);
|
|
}
|
|
|
|
static void
|
|
egismoc_set_print_data (FpPrint *print,
|
|
const guchar *device_print_id)
|
|
{
|
|
g_autofree gchar *user_id = g_malloc (EGISMOC_FINGERPRINT_DATA_SIZE + 1);
|
|
GVariant *print_id_var = NULL;
|
|
GVariant *fpi_data = NULL;
|
|
|
|
memcpy (user_id, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
memset (user_id + EGISMOC_FINGERPRINT_DATA_SIZE, '\0', sizeof (gchar));
|
|
|
|
fpi_print_fill_from_user_id (print, user_id);
|
|
fpi_print_set_type (print, FPI_PRINT_RAW);
|
|
fpi_print_set_device_stored (print, TRUE);
|
|
|
|
g_object_set (print, "description", user_id, NULL);
|
|
|
|
print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
|
device_print_id,
|
|
EGISMOC_FINGERPRINT_DATA_SIZE,
|
|
sizeof (guchar));
|
|
fpi_data = g_variant_new ("(@ay)", print_id_var);
|
|
g_object_set (print, "fpi-data", fpi_data, NULL);
|
|
}
|
|
|
|
static GPtrArray *
|
|
egismoc_get_enrolled_prints (FpDevice *device)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref);
|
|
FpPrint *print = NULL;
|
|
|
|
for (int i = 0; i < self->enrolled_num; i++)
|
|
{
|
|
print = fp_print_new (device);
|
|
egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i));
|
|
g_ptr_array_add (result, g_object_ref_sink (print));
|
|
}
|
|
|
|
return g_steal_pointer (&result);
|
|
}
|
|
|
|
static void
|
|
egismoc_list_fill_enrolled_ids_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("List callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
if (self->enrolled_ids)
|
|
g_ptr_array_free (self->enrolled_ids, TRUE);
|
|
self->enrolled_ids = g_ptr_array_new_with_free_func (g_free);
|
|
self->enrolled_num = 0;
|
|
|
|
/*
|
|
Each fingerprint ID will be returned in this response as a 32 byte array
|
|
The other stuff in the payload is 16 bytes long, so if there is at least 1 print
|
|
then the length should be at least 16+32=48 bytes long
|
|
*/
|
|
for (int pos = EGISMOC_LIST_RESPONSE_PREFIX_SIZE;
|
|
pos < length_in - EGISMOC_LIST_RESPONSE_SUFFIX_SIZE;
|
|
pos += EGISMOC_FINGERPRINT_DATA_SIZE, self->enrolled_num++)
|
|
{
|
|
g_autofree guchar *print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
memcpy (print_id, buffer_in + pos, EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
fp_dbg ("Device fingerprint %0d: %.*s%c", self->enrolled_num, EGISMOC_FINGERPRINT_DATA_SIZE, print_id, '\0');
|
|
g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id));
|
|
}
|
|
|
|
fp_info ("Number of currently enrolled fingerprints on the device is %d", self->enrolled_num);
|
|
|
|
if (self->task_ssm)
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
|
|
static void
|
|
egismoc_list_run_state (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
g_autoptr(GPtrArray) enrolled_prints = NULL;
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case LIST_GET_ENROLLED_IDS:
|
|
egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb);
|
|
break;
|
|
|
|
case LIST_RETURN_ENROLLED_PRINTS:
|
|
enrolled_prints = egismoc_get_enrolled_prints (device);
|
|
fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL);
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_list (FpDevice *device)
|
|
{
|
|
fp_dbg ("List");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
self->task_ssm = fpi_ssm_new (device,
|
|
egismoc_list_run_state,
|
|
LIST_STATES);
|
|
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
|
|
}
|
|
|
|
static guchar *
|
|
egismoc_get_delete_cmd (FpDevice *device,
|
|
FpPrint *delete_print,
|
|
gsize *length_out)
|
|
{
|
|
fp_dbg ("Get delete command");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
g_autofree const gchar *print_description = NULL;
|
|
|
|
g_autoptr(GVariant) print_data = NULL;
|
|
g_autoptr(GVariant) print_data_id_var = NULL;
|
|
const guchar *print_data_id = NULL;
|
|
gsize print_data_id_len = 0;
|
|
g_autofree guchar *enrolled_print_id = NULL;
|
|
guchar *result = NULL;
|
|
gsize pos = 0;
|
|
|
|
/*
|
|
The final command body should contain:
|
|
1) hard-coded 00 00
|
|
2) 2-byte size indiciator, 20*Number deleted identifiers plus 7 in form of: num_to_delete * 0x20 + 0x07
|
|
Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109)
|
|
3) Hard-coded prefix (cmd_delete_prefix)
|
|
4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 7 (num_to_delete * 0x20)
|
|
5) All of the currently registered prints to delete in their 32-byte device identifiers (enrolled_list)
|
|
*/
|
|
|
|
const int num_to_delete = (!delete_print) ? self->enrolled_num : 1;
|
|
const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE * num_to_delete;
|
|
/* total_length is the 6 various bytes plus prefix and body payload */
|
|
const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + body_length;
|
|
|
|
/* pre-fill entire payload with 00s */
|
|
result = g_malloc0 (total_length);
|
|
|
|
/* start with 00 00 (just move starting offset up by 2) */
|
|
pos = 2;
|
|
|
|
/* Size Counter bytes */
|
|
/* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */
|
|
/* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */
|
|
if (num_to_delete > 7)
|
|
{
|
|
memset (result + pos, 0x01, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, ((num_to_delete - 8) * 0x20) + 0x07, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
else
|
|
{
|
|
/* first byte is 0x00, just skip it */
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, (num_to_delete * 0x20) + 0x07, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
|
|
/* command prefix */
|
|
memcpy (result + pos, cmd_delete_prefix, cmd_delete_prefix_len);
|
|
pos += cmd_delete_prefix_len;
|
|
|
|
/* 2-bytes size logic for counter again */
|
|
if (num_to_delete > 7)
|
|
{
|
|
memset (result + pos, 0x01, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, ((num_to_delete - 8) * 0x20), sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
else
|
|
{
|
|
/* first byte is 0x00, just skip it */
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, (num_to_delete * 0x20), sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
|
|
/* append desired 32-byte fingerprint IDs */
|
|
/* if passed a delete_print then fetch its data from the FpPrint */
|
|
if (delete_print)
|
|
{
|
|
g_object_get (delete_print, "description", &print_description, NULL);
|
|
g_object_get (delete_print, "fpi-data", &print_data, NULL);
|
|
|
|
if (!g_variant_check_format_string (print_data, "(@ay)", FALSE))
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID));
|
|
return NULL;
|
|
}
|
|
|
|
g_variant_get (print_data, "(@ay)", &print_data_id_var);
|
|
print_data_id = g_variant_get_fixed_array (print_data_id_var, &print_data_id_len, sizeof (guchar));
|
|
|
|
if (!g_str_has_prefix (print_description, "FP"))
|
|
fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.", print_description);
|
|
|
|
fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id);
|
|
|
|
memcpy (result + pos, print_data_id, EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
}
|
|
/* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */
|
|
else
|
|
{
|
|
for (int i = 0; i < self->enrolled_ids->len; i++)
|
|
memcpy (result + pos + (EGISMOC_FINGERPRINT_DATA_SIZE * i),
|
|
g_ptr_array_index (self->enrolled_ids, i),
|
|
EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
}
|
|
pos += body_length;
|
|
|
|
if (length_out)
|
|
*length_out = total_length;
|
|
|
|
return g_steal_pointer (&result);
|
|
}
|
|
|
|
static void
|
|
egismoc_delete_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Delete callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* Check that the read payload indicates "success" with the delete */
|
|
if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_delete_success_prefix,
|
|
rsp_delete_success_prefix_len))
|
|
{
|
|
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_CLEAR_STORAGE)
|
|
{
|
|
fpi_device_clear_storage_complete (device, NULL);
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
else if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE)
|
|
{
|
|
fpi_device_delete_complete (device, NULL);
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Unsupported delete action."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Delete print was not successfull"));
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_delete_run_state (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
g_autofree guchar *payload = NULL;
|
|
gsize payload_length = 0;
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case DELETE_GET_ENROLLED_IDS:
|
|
/* get enrolled_ids and enrolled_num from device for use building delete payload below */
|
|
egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb);
|
|
break;
|
|
|
|
case DELETE_DELETE:
|
|
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE)
|
|
payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm), &payload_length);
|
|
else
|
|
payload = egismoc_get_delete_cmd (device, NULL, &payload_length);
|
|
|
|
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_delete_cb);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_clear_storage (FpDevice *device)
|
|
{
|
|
fp_dbg ("Clear storage");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
self->task_ssm = fpi_ssm_new (device,
|
|
egismoc_delete_run_state,
|
|
DELETE_STATES);
|
|
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
|
|
}
|
|
|
|
static void
|
|
egismoc_delete (FpDevice *device)
|
|
{
|
|
fp_dbg ("Delete");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
g_autoptr(FpPrint) delete_print = NULL;
|
|
|
|
fpi_device_get_delete_data (device, &delete_print);
|
|
|
|
self->task_ssm = fpi_ssm_new (device,
|
|
egismoc_delete_run_state,
|
|
DELETE_STATES);
|
|
fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&delete_print), NULL); /* todo leak or cleared by libfprint ? */
|
|
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
|
|
}
|
|
|
|
static void
|
|
egismoc_enroll_status_report (FpDevice *device,
|
|
EnrollPrint *enroll_print,
|
|
EnrollStatus status,
|
|
GError *error)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
switch (status)
|
|
{
|
|
case ENROLL_STATUS_DEVICE_FULL:
|
|
case ENROLL_STATUS_DUPLICATE:
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
break;
|
|
|
|
case ENROLL_STATUS_RETRY:
|
|
fpi_device_enroll_progress (device, enroll_print->stage, NULL, error);
|
|
break;
|
|
|
|
case ENROLL_STATUS_PARTIAL_OK:
|
|
enroll_print->stage++;
|
|
fp_info ("Partial capture successful. Please touch the sensor again (%d/%d)",
|
|
enroll_print->stage,
|
|
EGISMOC_MAX_ENROLL_NUM);
|
|
fpi_device_enroll_progress (device, enroll_print->stage, enroll_print->print, NULL);
|
|
break;
|
|
|
|
case ENROLL_STATUS_COMPLETE:
|
|
fp_info ("Enrollment was successful!");
|
|
fpi_device_enroll_complete (device, g_object_ref (enroll_print->print), NULL);
|
|
break;
|
|
|
|
default:
|
|
if (error)
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
else
|
|
fpi_ssm_mark_failed (self->task_ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, "Unknown error"));
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_read_capture_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Read capture callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
EnrollPrint *enroll_print = fpi_ssm_get_data (self->task_ssm);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* Check that the read payload indicates "success" */
|
|
if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_read_success_prefix,
|
|
rsp_read_success_prefix_len) &&
|
|
egismoc_validate_response_suffix (buffer_in,
|
|
length_in,
|
|
rsp_read_success_suffix,
|
|
rsp_read_success_suffix_len))
|
|
{
|
|
egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_PARTIAL_OK, NULL);
|
|
}
|
|
else
|
|
{
|
|
/* If not success then the sensor can either report "off center" or "sensor is dirty" */
|
|
|
|
/* "Off center" */
|
|
if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_read_offcenter_prefix,
|
|
rsp_read_offcenter_prefix_len) &&
|
|
egismoc_validate_response_suffix (buffer_in,
|
|
length_in,
|
|
rsp_read_offcenter_suffix,
|
|
rsp_read_offcenter_suffix_len))
|
|
error = fpi_device_retry_new (FP_DEVICE_RETRY_CENTER_FINGER);
|
|
|
|
/* "Sensor is dirty" */
|
|
else if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_read_dirty_prefix,
|
|
rsp_read_dirty_prefix_len))
|
|
error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER,
|
|
"Your device is having trouble recognizing you. Make sure your sensor is clean.");
|
|
|
|
else
|
|
error = fpi_device_retry_new_msg (FP_DEVICE_RETRY_REMOVE_FINGER,
|
|
"Unknown failure trying to read your finger. Please try again.");
|
|
|
|
egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_RETRY, error);
|
|
}
|
|
|
|
if (enroll_print->stage == EGISMOC_ENROLL_TIMES)
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
else
|
|
fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET);
|
|
}
|
|
|
|
static void
|
|
egismoc_enroll_check_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Enroll check callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* Check that the read payload reports "not yet enrolled" */
|
|
if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_check_not_yet_enrolled_prefix,
|
|
rsp_check_not_yet_enrolled_prefix_len))
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
else
|
|
egismoc_enroll_status_report (device, NULL, ENROLL_STATUS_DUPLICATE,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_DUPLICATE));
|
|
}
|
|
|
|
/*
|
|
Builds the full "check" payload which includes identifiers for all fingerprints which currently
|
|
should exist on the storage. This payload is used during both enrollment and verify actions.
|
|
*/
|
|
static guchar *
|
|
egismoc_get_check_cmd (FpDevice *device,
|
|
gsize *length_out)
|
|
{
|
|
fp_dbg ("Get check command");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
guchar *device_print_id = NULL;
|
|
guchar *result = NULL;
|
|
gsize pos = 0;
|
|
|
|
/*
|
|
The final command body should contain:
|
|
1) hard-coded 00 00
|
|
2) 2-byte size indiciator, 20*Number enrolled identifiers plus 9 in form of: (enrolled_num + 1) * 0x20 + 0x09
|
|
Since max prints can be higher than 7 then this goes up to 2 bytes (e9 + 9 = 109)
|
|
3) Hard-coded prefix (cmd_check_prefix)
|
|
4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9 ((enrolled_num + 1) * 0x20)
|
|
5) Hard-coded 32 * 0x00 bytes
|
|
6) All of the currently registered prints in their 32-byte device identifiers (enrolled_list)
|
|
7) Hard-coded suffix (cmd_check_suffix)
|
|
*/
|
|
|
|
const gsize body_length = sizeof (guchar) * self->enrolled_num * EGISMOC_FINGERPRINT_DATA_SIZE;
|
|
|
|
/* total_length is the 6 various bytes plus all other prefixes/suffixes and the body payload */
|
|
const gsize total_length = (sizeof (guchar) * 6)
|
|
+ cmd_check_prefix_len
|
|
+ EGISMOC_CMD_CHECK_SEPARATOR_LENGTH
|
|
+ body_length
|
|
+ cmd_check_suffix_len;
|
|
|
|
/* pre-fill entire payload with 00s */
|
|
result = g_malloc0 (total_length);
|
|
|
|
/* start with 00 00 (just move starting offset up by 2) */
|
|
pos = 2;
|
|
|
|
/* Size Counter bytes */
|
|
/* "easiest" way to handle 2-bytes size for counter is to hard-code logic for when we go to the 2nd byte */
|
|
/* note this will not work in case any model ever supports more than 14 prints (assumed max is 10) */
|
|
if (self->enrolled_num > 6)
|
|
{
|
|
memset (result + pos, 0x01, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, ((self->enrolled_num - 7) * 0x20) + 0x09, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
else
|
|
{
|
|
/* first byte is 0x00, just skip it */
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, ((self->enrolled_num + 1) * 0x20) + 0x09, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
|
|
/* command prefix */
|
|
memcpy (result + pos, cmd_check_prefix, cmd_check_prefix_len);
|
|
pos += cmd_check_prefix_len;
|
|
|
|
/* 2-bytes size logic for counter again */
|
|
if (self->enrolled_num > 6)
|
|
{
|
|
memset (result + pos, 0x01, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, (self->enrolled_num - 7) * 0x20, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
else
|
|
{
|
|
/* first byte is 0x00, just skip it */
|
|
pos += sizeof (guchar);
|
|
memset (result + pos, (self->enrolled_num + 1) * 0x20, sizeof (guchar));
|
|
pos += sizeof (guchar);
|
|
}
|
|
|
|
/* add 00s "separator" to offset position */
|
|
pos += EGISMOC_CMD_CHECK_SEPARATOR_LENGTH;
|
|
|
|
/* append all currently registered 32-byte fingerprint IDs */
|
|
const gsize print_id_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE;
|
|
|
|
for (int i = 0; i < self->enrolled_num; i++)
|
|
{
|
|
device_print_id = g_ptr_array_index (self->enrolled_ids, i);
|
|
memcpy (result + pos + (print_id_length * i), device_print_id, print_id_length);
|
|
}
|
|
pos += body_length;
|
|
|
|
/* command suffix */
|
|
memcpy (result + pos, cmd_check_suffix, cmd_check_suffix_len);
|
|
pos += cmd_check_suffix_len;
|
|
|
|
if (length_out)
|
|
*length_out = total_length;
|
|
|
|
return g_steal_pointer (&result);
|
|
}
|
|
|
|
static void
|
|
egismoc_enroll_run_state (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
EnrollPrint *enroll_print = fpi_ssm_get_data (ssm);
|
|
g_autofree guchar *payload = NULL;
|
|
gsize payload_length = 0;
|
|
g_autofree guchar *device_print_id = NULL;
|
|
g_autofree gchar *user_id = NULL;
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case ENROLL_GET_ENROLLED_IDS:
|
|
/* get enrolled_ids and enrolled_num from device for use in check stages below */
|
|
egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb);
|
|
break;
|
|
|
|
case ENROLL_CHECK_ENROLLED_NUM:
|
|
if (self->enrolled_num >= EGISMOC_MAX_ENROLL_NUM)
|
|
{
|
|
egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_DEVICE_FULL,
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_FULL));
|
|
return;
|
|
}
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case ENROLL_SENSOR_RESET:
|
|
egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_SENSOR_ENROLL:
|
|
egismoc_exec_cmd (device, cmd_sensor_enroll, cmd_sensor_enroll_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_WAIT_FINGER:
|
|
egismoc_wait_finger_on_sensor (ssm, device);
|
|
break;
|
|
|
|
case ENROLL_SENSOR_CHECK:
|
|
egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_CHECK:
|
|
payload = egismoc_get_check_cmd (device, &payload_length);
|
|
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_enroll_check_cb);
|
|
break;
|
|
|
|
case ENROLL_START:
|
|
egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_CAPTURE_SENSOR_RESET:
|
|
egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_CAPTURE_SENSOR_START_CAPTURE:
|
|
egismoc_exec_cmd (device, cmd_sensor_start_capture, cmd_sensor_start_capture_len, NULL,
|
|
egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_CAPTURE_WAIT_FINGER:
|
|
egismoc_wait_finger_on_sensor (ssm, device);
|
|
break;
|
|
|
|
case ENROLL_CAPTURE_READ_RESPONSE:
|
|
egismoc_exec_cmd (device, cmd_read_capture, cmd_read_capture_len, NULL, egismoc_read_capture_cb);
|
|
break;
|
|
|
|
case ENROLL_COMMIT_START:
|
|
egismoc_exec_cmd (device, cmd_commit_starting, cmd_commit_starting_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_COMMIT:
|
|
user_id = fpi_print_generate_user_id (enroll_print->print);
|
|
fp_dbg ("New fingerprint ID: %s", user_id);
|
|
|
|
device_print_id = g_malloc0 (EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
memcpy (device_print_id, user_id, MIN (strlen (user_id), EGISMOC_FINGERPRINT_DATA_SIZE));
|
|
egismoc_set_print_data (enroll_print->print, device_print_id);
|
|
|
|
/* create new dynamic payload of cmd_new_print_prefix + device_print_id */
|
|
payload_length = cmd_new_print_prefix_len + EGISMOC_FINGERPRINT_DATA_SIZE;
|
|
payload = g_malloc0 (payload_length);
|
|
memcpy (payload, cmd_new_print_prefix, cmd_new_print_prefix_len);
|
|
memcpy (payload + cmd_new_print_prefix_len, device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
|
|
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_COMMIT_SENSOR_RESET:
|
|
egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case ENROLL_COMPLETE:
|
|
egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL);
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_enroll (FpDevice *device)
|
|
{
|
|
fp_dbg ("Enroll");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1);
|
|
|
|
fpi_device_get_enroll_data (device, &enroll_print->print);
|
|
enroll_print->stage = 0;
|
|
|
|
self->task_ssm = fpi_ssm_new (device, egismoc_enroll_run_state, ENROLL_STATES);
|
|
fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&enroll_print), g_free);
|
|
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
|
|
}
|
|
|
|
static void
|
|
egismoc_identify_check_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Identify check callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
guchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE];
|
|
FpPrint *print = NULL;
|
|
FpPrint *verify_print = NULL;
|
|
GPtrArray *prints;
|
|
gboolean found = FALSE;
|
|
guint index;
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* Check that the read payload indicates "match" */
|
|
if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_identify_match_prefix,
|
|
rsp_identify_match_prefix_len) &&
|
|
egismoc_validate_response_suffix (buffer_in,
|
|
length_in,
|
|
rsp_identify_match_suffix,
|
|
rsp_identify_match_suffix_len))
|
|
{
|
|
/*
|
|
On success, there is a 32 byte array of "something"(?) in chars 14-45
|
|
and then the 32 byte array ID of the matched print comes as chars 46-77
|
|
*/
|
|
memcpy (device_print_id,
|
|
buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET,
|
|
EGISMOC_FINGERPRINT_DATA_SIZE);
|
|
|
|
/* Create a new print from this ID and then see if it matches the one indicated */
|
|
print = fp_print_new (device);
|
|
egismoc_set_print_data (print, device_print_id);
|
|
|
|
if (!print)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
|
|
"Failed to build a print from device response."));
|
|
return;
|
|
}
|
|
|
|
fp_info ("Identify successful for: %s", fp_print_get_description (print));
|
|
|
|
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY)
|
|
{
|
|
fpi_device_get_identify_data (device, &prints);
|
|
found = g_ptr_array_find_with_equal_func (prints,
|
|
print,
|
|
(GEqualFunc) fp_print_equal,
|
|
&index);
|
|
|
|
if (found)
|
|
fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL);
|
|
else
|
|
fpi_device_identify_report (device, NULL, print, NULL);
|
|
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
else
|
|
{
|
|
fpi_device_get_verify_data (device, &verify_print);
|
|
fp_info ("Verifying against: %s", fp_print_get_description (verify_print));
|
|
|
|
if (fp_print_equal (verify_print, print))
|
|
fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL);
|
|
else
|
|
fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL);
|
|
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
}
|
|
/* If device was successfully read but it was a "not matched" */
|
|
else if (egismoc_validate_response_prefix (buffer_in,
|
|
length_in,
|
|
rsp_identify_notmatch_prefix,
|
|
rsp_identify_notmatch_prefix_len))
|
|
{
|
|
fp_info ("Print was not identified by the device");
|
|
|
|
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
|
|
{
|
|
fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL);
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
else
|
|
{
|
|
fpi_device_identify_report (device, NULL, NULL, NULL);
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Unrecognized response from device."));
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_identify_run_state (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
g_autofree guchar *payload = NULL;
|
|
gsize payload_length = 0;
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case IDENTIFY_GET_ENROLLED_IDS:
|
|
/* get enrolled_ids and enrolled_num from device for use in check stages below */
|
|
egismoc_exec_cmd (device, cmd_list, cmd_list_len, NULL, egismoc_list_fill_enrolled_ids_cb);
|
|
break;
|
|
|
|
case IDENTIFY_CHECK_ENROLLED_NUM:
|
|
if (self->enrolled_num == 0)
|
|
{
|
|
fpi_ssm_mark_failed (g_steal_pointer (&self->task_ssm),
|
|
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
|
return;
|
|
}
|
|
fpi_ssm_next_state (ssm);
|
|
break;
|
|
|
|
case IDENTIFY_SENSOR_RESET:
|
|
egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case IDENTIFY_SENSOR_IDENTIFY:
|
|
egismoc_exec_cmd (device, cmd_sensor_identify, cmd_sensor_identify_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case IDENTIFY_WAIT_FINGER:
|
|
egismoc_wait_finger_on_sensor (ssm, device);
|
|
break;
|
|
|
|
case IDENTIFY_SENSOR_CHECK:
|
|
egismoc_exec_cmd (device, cmd_sensor_check, cmd_sensor_check_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
case IDENTIFY_CHECK:
|
|
payload = egismoc_get_check_cmd (device, &payload_length);
|
|
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length, g_free, egismoc_identify_check_cb);
|
|
break;
|
|
|
|
case IDENTIFY_COMPLETE_SENSOR_RESET:
|
|
egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len, NULL, egismoc_task_ssm_next_state_cb);
|
|
break;
|
|
|
|
/*
|
|
In Windows, the driver seems at this point to then immediately take another read from the sensor;
|
|
this is suspected to be an on-chip "verify". However, because the user's finger is still on the
|
|
sensor from the identify, then it seems to always return positive. We will consider this extra
|
|
step unnecessary and just skip it in this driver. This driver will instead handle matching of the
|
|
FpPrint from the gallery in the "verify" case of the callback egismoc_identify_check_cb.
|
|
*/
|
|
|
|
case IDENTIFY_COMPLETE:
|
|
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY)
|
|
fpi_device_identify_complete (device, NULL);
|
|
else
|
|
fpi_device_verify_complete (device, NULL);
|
|
|
|
fpi_ssm_mark_completed (ssm);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
egismoc_identify_verify (FpDevice *device)
|
|
{
|
|
fp_dbg ("Identify or Verify");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES);
|
|
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
|
|
}
|
|
|
|
static void
|
|
egismoc_fw_version_cb (FpDevice *device,
|
|
guchar *buffer_in,
|
|
gsize length_in,
|
|
GError *error)
|
|
{
|
|
fp_dbg ("Firmware version callback");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
g_autofree guchar *fw_version = NULL;
|
|
gsize prefix_length;
|
|
gsize fw_version_length;
|
|
|
|
if (error)
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm, error);
|
|
return;
|
|
}
|
|
|
|
/* Check that the read payload indicates "success" */
|
|
if (!egismoc_validate_response_suffix (buffer_in,
|
|
length_in,
|
|
rsp_fw_version_suffix,
|
|
rsp_fw_version_suffix_len))
|
|
{
|
|
fpi_ssm_mark_failed (self->task_ssm,
|
|
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
|
"Device firmware response was not valid."));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
FW Version is 12 bytes: a carriage return (0x0d) plus the version string itself.
|
|
Always skip [the read prefix] + [2 * check bytes] + [3 * 0x00] that come with every payload
|
|
Then we will also skip the carriage return and take all but the last 2 bytes as the FW Version
|
|
*/
|
|
prefix_length = egismoc_read_prefix_len + 2 + 3 + 1;
|
|
fw_version_length = length_in - prefix_length - rsp_fw_version_suffix_len;
|
|
fw_version = g_malloc0 (fw_version_length + 1);
|
|
|
|
memcpy (fw_version,
|
|
buffer_in + prefix_length,
|
|
length_in - prefix_length - rsp_fw_version_suffix_len);
|
|
*(fw_version + fw_version_length) = '\0';
|
|
|
|
fp_info ("Device firmware version is %s", fw_version);
|
|
|
|
fpi_ssm_next_state (self->task_ssm);
|
|
}
|
|
|
|
static void
|
|
egismoc_dev_init_done (FpiSsm *ssm,
|
|
FpDevice *device,
|
|
GError *error)
|
|
{
|
|
if (error)
|
|
g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, NULL);
|
|
|
|
fpi_device_open_complete (device, error);
|
|
}
|
|
|
|
static void
|
|
egismoc_dev_init_handler (FpiSsm *ssm,
|
|
FpDevice *device)
|
|
{
|
|
g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device);
|
|
|
|
switch (fpi_ssm_get_cur_state (ssm))
|
|
{
|
|
case DEV_INIT_CONTROL1:
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
32, 0x0000, 4, 16);
|
|
goto send_control;
|
|
break;
|
|
|
|
case DEV_INIT_CONTROL2:
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
32, 0x0000, 4, 40);
|
|
goto send_control;
|
|
break;
|
|
|
|
case DEV_INIT_CONTROL3:
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_STANDARD,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0, 0x0000, 0, 2);
|
|
goto send_control;
|
|
break;
|
|
|
|
case DEV_INIT_CONTROL4:
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_STANDARD,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
0, 0x0000, 0, 2);
|
|
goto send_control;
|
|
break;
|
|
|
|
case DEV_INIT_CONTROL5:
|
|
fpi_usb_transfer_fill_control (transfer,
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
82, 0x0000, 0, 8);
|
|
goto send_control;
|
|
break;
|
|
|
|
case DEV_GET_FW_VERSION:
|
|
egismoc_exec_cmd (device, cmd_fw_version, cmd_fw_version_len, NULL, egismoc_fw_version_cb);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
|
|
send_control:
|
|
transfer->ssm = ssm;
|
|
transfer->short_is_error = TRUE;
|
|
fpi_usb_transfer_submit (g_steal_pointer (&transfer),
|
|
EGISMOC_USB_CONTROL_TIMEOUT,
|
|
NULL,
|
|
fpi_ssm_usb_transfer_cb,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
egismoc_open (FpDevice *device)
|
|
{
|
|
fp_dbg ("Opening device");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
GError *error = NULL;
|
|
|
|
self->interrupt_cancellable = g_cancellable_new ();
|
|
|
|
if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error))
|
|
goto error;
|
|
|
|
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
|
|
goto error;
|
|
|
|
self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES);
|
|
fpi_ssm_start (self->task_ssm, egismoc_dev_init_done);
|
|
return;
|
|
|
|
error:
|
|
return fpi_device_open_complete (device, error);
|
|
}
|
|
|
|
static void
|
|
egismoc_cancel (FpDevice *device)
|
|
{
|
|
fp_dbg ("Cancel");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
|
|
g_cancellable_cancel (self->interrupt_cancellable);
|
|
g_clear_object (&self->interrupt_cancellable);
|
|
self->interrupt_cancellable = g_cancellable_new ();
|
|
}
|
|
|
|
static void
|
|
egismoc_close (FpDevice *device)
|
|
{
|
|
fp_dbg ("Closing device");
|
|
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
|
|
GError *error = NULL;
|
|
|
|
egismoc_cancel (device);
|
|
|
|
g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error);
|
|
fpi_device_close_complete (device, error);
|
|
|
|
if (self->task_ssm)
|
|
fpi_ssm_free (self->task_ssm);
|
|
self->task_ssm = NULL;
|
|
|
|
if (self->cmd_ssm)
|
|
fpi_ssm_free (self->cmd_ssm);
|
|
self->cmd_ssm = NULL;
|
|
|
|
self->cmd_transfer = NULL;
|
|
|
|
g_clear_object (&self->interrupt_cancellable);
|
|
|
|
if (self->enrolled_ids)
|
|
g_ptr_array_free (self->enrolled_ids, TRUE);
|
|
self->enrolled_ids = NULL;
|
|
self->enrolled_num = -1;
|
|
}
|
|
|
|
static void
|
|
fpi_device_egismoc_init (FpiDeviceEgisMoc *self)
|
|
{
|
|
G_DEBUG_HERE ();
|
|
}
|
|
|
|
static void
|
|
fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass)
|
|
{
|
|
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
|
|
|
|
dev_class->id = FP_COMPONENT;
|
|
dev_class->full_name = EGISMOC_DRIVER_FULLNAME;
|
|
|
|
dev_class->type = FP_DEVICE_TYPE_USB;
|
|
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
|
dev_class->id_table = egismoc_id_table;
|
|
dev_class->nr_enroll_stages = EGISMOC_ENROLL_TIMES;
|
|
dev_class->temp_hot_seconds = 0; /* device should be "always off" unless being used */
|
|
|
|
dev_class->open = egismoc_open;
|
|
dev_class->cancel = egismoc_cancel;
|
|
dev_class->suspend = egismoc_cancel;
|
|
dev_class->close = egismoc_close;
|
|
dev_class->identify = egismoc_identify_verify;
|
|
dev_class->verify = egismoc_identify_verify;
|
|
dev_class->enroll = egismoc_enroll;
|
|
dev_class->delete = egismoc_delete;
|
|
dev_class->clear_storage = egismoc_clear_storage;
|
|
dev_class->list = egismoc_list;
|
|
|
|
fpi_device_class_auto_initialize_features (dev_class);
|
|
dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK;
|
|
}
|