mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-19 11:18:11 +02:00
Merge branch 'wip/validity-vcsfw-driver' into 'master'
Draft: validity: Add new driver for Validity/Synaptics VCSFW sensors See merge request libfprint/libfprint!579
This commit is contained in:
commit
2556069c47
36 changed files with 20653 additions and 9 deletions
|
|
@ -319,6 +319,14 @@ usb:v05BAp000A*
|
|||
ID_AUTOSUSPEND=1
|
||||
ID_PERSIST=0
|
||||
|
||||
# Supported by libfprint driver validity
|
||||
usb:v138Ap0090*
|
||||
usb:v138Ap0097*
|
||||
usb:v06CBp009A*
|
||||
usb:v138Ap009D*
|
||||
ID_AUTOSUSPEND=1
|
||||
ID_PERSIST=0
|
||||
|
||||
# Supported by libfprint driver vcom5s
|
||||
usb:v061Ap0110*
|
||||
ID_AUTOSUSPEND=1
|
||||
|
|
@ -388,7 +396,6 @@ usb:v06CBp0051*
|
|||
usb:v06CBp0081*
|
||||
usb:v06CBp0088*
|
||||
usb:v06CBp008A*
|
||||
usb:v06CBp009A*
|
||||
usb:v06CBp009B*
|
||||
usb:v06CBp00A1*
|
||||
usb:v06CBp00A2*
|
||||
|
|
@ -436,11 +443,8 @@ usb:v138Ap003A*
|
|||
usb:v138Ap003C*
|
||||
usb:v138Ap003D*
|
||||
usb:v138Ap003F*
|
||||
usb:v138Ap0090*
|
||||
usb:v138Ap0092*
|
||||
usb:v138Ap0094*
|
||||
usb:v138Ap0097*
|
||||
usb:v138Ap009D*
|
||||
usb:v138Ap00AB*
|
||||
usb:v138Ap00A6*
|
||||
usb:v147Ep1002*
|
||||
|
|
|
|||
|
|
@ -272,13 +272,14 @@ 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);
|
||||
CommandData *data = NULL;
|
||||
|
||||
self->cmd_ssm = NULL;
|
||||
/* Notify about the SSM failure from here instead. */
|
||||
if (error)
|
||||
{
|
||||
fp_err ("%s error: %s ", G_STRFUNC, error->message);
|
||||
data = fpi_ssm_get_data (ssm);
|
||||
if (data->callback)
|
||||
data->callback (self, NULL, error);
|
||||
}
|
||||
|
|
|
|||
1394
libfprint/drivers/validity/validity.c
Normal file
1394
libfprint/drivers/validity/validity.c
Normal file
File diff suppressed because it is too large
Load diff
346
libfprint/drivers/validity/validity.h
Normal file
346
libfprint/drivers/validity/validity.h
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* Validity/Synaptics VCSFW fingerprint sensor driver
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "validity_capture.h"
|
||||
#include "validity_data.h"
|
||||
#include "validity_db.h"
|
||||
#include "validity_pair.h"
|
||||
#include "validity_sensor.h"
|
||||
#include "validity_tls.h"
|
||||
|
||||
/* USB Endpoint addresses */
|
||||
#define VALIDITY_EP_CMD_OUT 0x01
|
||||
#define VALIDITY_EP_CMD_IN 0x81
|
||||
#define VALIDITY_EP_DATA_IN 0x82
|
||||
#define VALIDITY_EP_INT_IN 0x83
|
||||
|
||||
/* USB transfer parameters */
|
||||
#define VALIDITY_USB_TIMEOUT 15000
|
||||
#define VALIDITY_USB_INT_TIMEOUT 100
|
||||
#define VALIDITY_MAX_TRANSFER_LEN (100 * 1024)
|
||||
#define VALIDITY_USB_INT_DATA_SIZE 1024
|
||||
#define VALIDITY_USB_SEND_HEADER_LEN 1
|
||||
|
||||
/* Number of enroll stages */
|
||||
#define VALIDITY_ENROLL_STAGES 8
|
||||
|
||||
/* Interrupt response bits */
|
||||
#define VALIDITY_INT_FINGER_DOWN 0x02
|
||||
#define VALIDITY_INT_SCAN_COMPLETE 0x04
|
||||
|
||||
typedef enum {
|
||||
VALIDITY_DEV_90 = 0, /* 138a:0090 */
|
||||
VALIDITY_DEV_97, /* 138a:0097 */
|
||||
VALIDITY_DEV_9A, /* 06cb:009a */
|
||||
VALIDITY_DEV_9D, /* 138a:009d */
|
||||
} ValidityDeviceType;
|
||||
|
||||
/* Firmware version info from GET_VERSION (cmd 0x01) */
|
||||
typedef struct
|
||||
{
|
||||
guint32 build_time;
|
||||
guint32 build_num;
|
||||
guint8 version_major;
|
||||
guint8 version_minor;
|
||||
guint8 target;
|
||||
guint8 product;
|
||||
guint8 silicon_rev;
|
||||
guint8 formal_release;
|
||||
guint8 platform;
|
||||
guint8 patch;
|
||||
guint8 serial_number[6];
|
||||
guint16 security;
|
||||
guint8 iface;
|
||||
guint8 device_type;
|
||||
} ValidityVersionInfo;
|
||||
|
||||
/* Open SSM states */
|
||||
typedef enum {
|
||||
VALIDITY_OPEN_CLAIM_INTERFACE = 0,
|
||||
VALIDITY_OPEN_SEND_INIT,
|
||||
VALIDITY_OPEN_NUM_STATES,
|
||||
} ValidityOpenState;
|
||||
|
||||
/* Close SSM states */
|
||||
typedef enum {
|
||||
VALIDITY_CLOSE_RELEASE_INTERFACE = 0,
|
||||
VALIDITY_CLOSE_NUM_STATES,
|
||||
} ValidityCloseState;
|
||||
|
||||
/* Calibration SSM states (runs during open, after capture_setup) */
|
||||
typedef enum {
|
||||
CALIB_BUILD_CMD = 0,
|
||||
CALIB_SEND_CMD,
|
||||
CALIB_SEND_CMD_RECV,
|
||||
CALIB_READ_DATA,
|
||||
CALIB_AVERAGE_FRAMES,
|
||||
CALIB_PROCESS,
|
||||
CALIB_LOOP_CHECK,
|
||||
CALIB_BUILD_CLEAN_SLATE_CMD,
|
||||
CALIB_CLEAN_SLATE_SEND,
|
||||
CALIB_CLEAN_SLATE_RECV,
|
||||
CALIB_CLEAN_SLATE_READ,
|
||||
CALIB_SAVE_CLEAN_SLATE,
|
||||
CALIB_DONE,
|
||||
CALIB_NUM_STATES,
|
||||
} ValidityCalibState;
|
||||
|
||||
/* Enrollment SSM states — matches python-validity sensor.py Sensor.enroll() */
|
||||
typedef enum {
|
||||
ENROLL_CLEANUP_STALE = 0, /* Close any stale enrollment session */
|
||||
ENROLL_CLEANUP_STALE_RECV,
|
||||
/* Pre-enrollment: delete existing user records to avoid 0x0526 */
|
||||
ENROLL_PRE_GET_STORAGE,
|
||||
ENROLL_PRE_GET_STORAGE_RECV,
|
||||
ENROLL_PRE_DEL_USER,
|
||||
ENROLL_PRE_DEL_USER_RECV,
|
||||
ENROLL_START, /* create_enrollment (cmd 0x69 flag=1) */
|
||||
ENROLL_START_RECV,
|
||||
/* --- Per-iteration loop --- */
|
||||
ENROLL_LED_ON,
|
||||
ENROLL_LED_ON_RECV,
|
||||
ENROLL_WAIT_FINGER_DELAY,
|
||||
ENROLL_BUILD_CAPTURE,
|
||||
ENROLL_CAPTURE_SEND,
|
||||
ENROLL_CAPTURE_RECV,
|
||||
ENROLL_WAIT_FINGER,
|
||||
ENROLL_WAIT_SCAN_COMPLETE,
|
||||
ENROLL_GET_PRG_STATUS,
|
||||
ENROLL_GET_PRG_STATUS_RECV,
|
||||
ENROLL_CAPTURE_STOP,
|
||||
ENROLL_CAPTURE_STOP_RECV,
|
||||
ENROLL_UPDATE_START,
|
||||
ENROLL_UPDATE_START_RECV,
|
||||
ENROLL_WAIT_UPDATE_START_INT, /* PY: usb.wait_int() inside enrollment_update_start */
|
||||
ENROLL_DB_WRITE_ENABLE, /* PY: write_enable() before 1st enrollment_update */
|
||||
ENROLL_DB_WRITE_ENABLE_RECV,
|
||||
ENROLL_APPEND_IMAGE, /* 1st enrollment_update (trigger) */
|
||||
ENROLL_APPEND_IMAGE_RECV,
|
||||
ENROLL_CLEANUPS, /* PY: call_cleanups() after 1st enrollment_update */
|
||||
ENROLL_CLEANUPS_RECV,
|
||||
ENROLL_WAIT_UPDATE_INT, /* PY: usb.wait_int() between the two calls */
|
||||
ENROLL_DB_WRITE_ENABLE_READ, /* PY: write_enable() before 2nd enrollment_update */
|
||||
ENROLL_DB_WRITE_ENABLE_READ_RECV,
|
||||
ENROLL_APPEND_IMAGE_READ, /* 2nd enrollment_update (read result) */
|
||||
ENROLL_APPEND_IMAGE_READ_RECV,
|
||||
ENROLL_CLEANUPS_READ, /* PY: call_cleanups() after 2nd enrollment_update */
|
||||
ENROLL_CLEANUPS_READ_RECV,
|
||||
ENROLL_UPDATE_END, /* PY: enrollment_update_end = cmd 0x69 flag=0 (finally) */
|
||||
ENROLL_UPDATE_END_RECV,
|
||||
ENROLL_LOOP_CHECK,
|
||||
/* --- Post-loop: DB commit --- */
|
||||
ENROLL_UPDATE_END2, /* PY: 2nd enrollment_update_end after loop */
|
||||
ENROLL_UPDATE_END2_RECV,
|
||||
ENROLL_GET_STORAGE,
|
||||
ENROLL_GET_STORAGE_RECV,
|
||||
/* If storage doesn't exist (0x04b3), create it: */
|
||||
ENROLL_INIT_STORAGE_WE, /* db_write_enable for storage creation */
|
||||
ENROLL_INIT_STORAGE_WE_RECV,
|
||||
ENROLL_INIT_STORAGE_CREATE, /* new_record(1, 4, 3, "StgWindsor\0") */
|
||||
ENROLL_INIT_STORAGE_CREATE_RECV,
|
||||
ENROLL_INIT_STORAGE_CLEAN, /* call_cleanups */
|
||||
ENROLL_INIT_STORAGE_CLEAN_RECV,
|
||||
ENROLL_DB_WRITE_ENABLE2,
|
||||
ENROLL_DB_WRITE_ENABLE2_RECV,
|
||||
ENROLL_CREATE_USER,
|
||||
ENROLL_CREATE_USER_RECV,
|
||||
ENROLL_CREATE_USER_CLEANUPS,
|
||||
ENROLL_CREATE_USER_CLEANUPS_RECV,
|
||||
ENROLL_DB_WRITE_ENABLE3,
|
||||
ENROLL_DB_WRITE_ENABLE3_RECV,
|
||||
ENROLL_CREATE_FINGER,
|
||||
ENROLL_CREATE_FINGER_RECV,
|
||||
ENROLL_FINAL_CLEANUPS,
|
||||
ENROLL_FINAL_CLEANUPS_RECV,
|
||||
ENROLL_WAIT_FINGER_INT,
|
||||
ENROLL_LED_OFF, /* PY: glow_end_scan() — LAST step per PY */
|
||||
ENROLL_LED_OFF_RECV,
|
||||
ENROLL_DONE,
|
||||
ENROLL_NUM_STATES,
|
||||
} ValidityEnrollState;
|
||||
|
||||
/* Verify/Identify SSM states */
|
||||
typedef enum {
|
||||
VERIFY_LED_ON = 0,
|
||||
VERIFY_LED_ON_RECV,
|
||||
VERIFY_BUILD_CAPTURE,
|
||||
VERIFY_CAPTURE_SEND,
|
||||
VERIFY_CAPTURE_RECV,
|
||||
VERIFY_WAIT_FINGER,
|
||||
VERIFY_WAIT_SCAN_COMPLETE,
|
||||
VERIFY_GET_PRG_STATUS,
|
||||
VERIFY_GET_PRG_STATUS_RECV,
|
||||
VERIFY_CAPTURE_STOP,
|
||||
VERIFY_CAPTURE_STOP_RECV,
|
||||
VERIFY_MATCH_START,
|
||||
VERIFY_MATCH_START_RECV,
|
||||
VERIFY_WAIT_MATCH_INT,
|
||||
VERIFY_GET_RESULT,
|
||||
VERIFY_GET_RESULT_RECV,
|
||||
VERIFY_CLEANUP,
|
||||
VERIFY_CLEANUP_RECV,
|
||||
VERIFY_LED_OFF,
|
||||
VERIFY_LED_OFF_RECV,
|
||||
VERIFY_DONE,
|
||||
VERIFY_NUM_STATES,
|
||||
} ValidityVerifyState;
|
||||
|
||||
/* List prints SSM states */
|
||||
typedef enum {
|
||||
LIST_GET_STORAGE = 0,
|
||||
LIST_GET_STORAGE_RECV,
|
||||
LIST_GET_USER,
|
||||
LIST_GET_USER_RECV,
|
||||
LIST_DONE,
|
||||
LIST_NUM_STATES,
|
||||
} ValidityListState;
|
||||
|
||||
/* Delete print SSM states */
|
||||
typedef enum {
|
||||
DELETE_GET_STORAGE = 0,
|
||||
DELETE_GET_STORAGE_RECV,
|
||||
DELETE_LOOKUP_USER,
|
||||
DELETE_LOOKUP_USER_RECV,
|
||||
DELETE_DEL_RECORD,
|
||||
DELETE_DEL_RECORD_RECV,
|
||||
DELETE_DONE,
|
||||
DELETE_NUM_STATES,
|
||||
} ValidityDeleteState;
|
||||
|
||||
/* Clear storage SSM states */
|
||||
typedef enum {
|
||||
CLEAR_GET_STORAGE = 0,
|
||||
CLEAR_GET_STORAGE_RECV,
|
||||
CLEAR_DEL_USER,
|
||||
CLEAR_DEL_USER_RECV,
|
||||
CLEAR_DONE,
|
||||
CLEAR_NUM_STATES,
|
||||
} ValidityClearState;
|
||||
|
||||
#define FPI_TYPE_DEVICE_VALIDITY (fpi_device_validity_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (FpiDeviceValidity, fpi_device_validity,
|
||||
FPI, DEVICE_VALIDITY, FpDevice)
|
||||
|
||||
struct _FpiDeviceValidity
|
||||
{
|
||||
FpDevice parent;
|
||||
|
||||
ValidityDeviceType dev_type;
|
||||
ValidityVersionInfo version_info;
|
||||
GCancellable *interrupt_cancellable;
|
||||
|
||||
/* Runtime data files loaded from libfprint-validity-data package */
|
||||
ValidityDataStore device_data;
|
||||
ValidityDataStore common_data;
|
||||
|
||||
/* TLS session state */
|
||||
ValidityTlsState tls;
|
||||
|
||||
/* Sensor identification and HAL state (post-TLS) */
|
||||
ValiditySensorState sensor;
|
||||
|
||||
/* Capture program infrastructure and calibration state */
|
||||
ValidityCaptureState capture;
|
||||
|
||||
/* Firmware extension status */
|
||||
gboolean fwext_loaded;
|
||||
|
||||
/* Pairing state (for uninitialized devices) */
|
||||
ValidityPairState pair_state;
|
||||
|
||||
/* Calibration state */
|
||||
gboolean calibrated;
|
||||
guint calib_iteration;
|
||||
|
||||
/* Enrollment state */
|
||||
guint32 enroll_key;
|
||||
guint8 *enroll_template;
|
||||
gsize enroll_template_len;
|
||||
guint enroll_stage;
|
||||
guint16 enroll_user_dbid;
|
||||
guint16 enroll_storage_dbid;
|
||||
guint scan_incomplete_count;
|
||||
|
||||
/* Verify/identify mode flag: TRUE = identify, FALSE = verify */
|
||||
gboolean identify_mode;
|
||||
|
||||
/* List prints state */
|
||||
ValidityUserStorage list_storage;
|
||||
guint list_user_idx;
|
||||
|
||||
/* Delete state */
|
||||
guint16 delete_storage_dbid;
|
||||
guint16 delete_finger_subtype;
|
||||
guint16 delete_finger_dbid;
|
||||
|
||||
/* Command SSM: manages the send-cmd/recv-response cycle */
|
||||
FpiSsm *cmd_ssm;
|
||||
|
||||
/* Parent SSM: back-pointer for non-subsm child SSMs */
|
||||
FpiSsm *open_ssm;
|
||||
|
||||
/* Pending response data stashed for higher-level SSM consumption */
|
||||
guint16 cmd_response_status;
|
||||
guint8 *cmd_response_data;
|
||||
gsize cmd_response_len;
|
||||
|
||||
/* Bulk data buffer (EP 0x82 reads during capture/calibration) */
|
||||
guint8 *bulk_data;
|
||||
gsize bulk_data_len;
|
||||
|
||||
/* Emulation mode: in-memory print storage (element-type FpPrint) */
|
||||
GPtrArray *emulation_prints;
|
||||
};
|
||||
|
||||
/* Enrollment SSM (validity_enroll.c) */
|
||||
void validity_enroll (FpDevice *device);
|
||||
|
||||
/* Enrollment response parsing — exposed for unit testing */
|
||||
#define ENROLLMENT_MAGIC_LEN 0x38
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint8 *header;
|
||||
gsize header_len;
|
||||
guint8 *template_data;
|
||||
gsize template_len;
|
||||
guint8 *tid;
|
||||
gsize tid_len;
|
||||
} EnrollmentUpdateResult;
|
||||
|
||||
void enrollment_update_result_clear (EnrollmentUpdateResult *r);
|
||||
gboolean parse_enrollment_update_response (const guint8 *data,
|
||||
gsize data_len,
|
||||
EnrollmentUpdateResult *result);
|
||||
|
||||
/* Verify/Identify SSMs (validity_verify.c) */
|
||||
void validity_verify (FpDevice *device);
|
||||
void validity_identify (FpDevice *device);
|
||||
void validity_list (FpDevice *device);
|
||||
void validity_delete (FpDevice *device);
|
||||
void validity_clear_storage (FpDevice *device);
|
||||
|
||||
/* Gallery matching helper (validity_verify.c) */
|
||||
FpPrint *validity_find_gallery_match (GPtrArray *gallery,
|
||||
guint16 subtype);
|
||||
1848
libfprint/drivers/validity/validity_capture.c
Normal file
1848
libfprint/drivers/validity/validity_capture.c
Normal file
File diff suppressed because it is too large
Load diff
396
libfprint/drivers/validity/validity_capture.h
Normal file
396
libfprint/drivers/validity/validity_capture.h
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
* Capture program infrastructure for Validity/Synaptics VCSFW sensors
|
||||
*
|
||||
* Implements the capture command builder (cmd 0x02), timeslot table
|
||||
* patching, factory calibration parsing, frame averaging, and
|
||||
* calibration data processing.
|
||||
*
|
||||
* Reference: python-validity sensor.py, timeslot.py, generated_tables.py
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include "validity_sensor.h"
|
||||
|
||||
/* ================================================================
|
||||
* Capture mode — passed to build_cmd_02 to select capture behavior
|
||||
* Values match python-validity CaptureMode enum.
|
||||
* ================================================================ */
|
||||
typedef enum {
|
||||
VALIDITY_CAPTURE_CALIBRATE = 1,
|
||||
VALIDITY_CAPTURE_IDENTIFY = 2,
|
||||
VALIDITY_CAPTURE_ENROLL = 3,
|
||||
VALIDITY_CAPTURE_ENROLL_IDENTIFY = 4, /* IDENTIFY chunk (0x4e) + ENROLL recon (0x23) */
|
||||
} ValidityCaptureMode;
|
||||
|
||||
/* ================================================================
|
||||
* Capture program chunk — TLV entry from the CaptureProg table.
|
||||
* Format: type(2LE) | size(2LE) | data(size bytes)
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 type;
|
||||
guint16 size;
|
||||
guint8 *data; /* owned; g_free() when done */
|
||||
} ValidityCaptureChunk;
|
||||
|
||||
/* ================================================================
|
||||
* Capture state — kept in the device struct, initialized during open
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
/* Capture program binary (from CaptureProg table lookup) */
|
||||
const guint8 *capture_prog;
|
||||
gsize capture_prog_len;
|
||||
|
||||
/* Geometry derived from SensorTypeInfo + CaptureProg */
|
||||
guint16 lines_per_frame;
|
||||
guint16 bytes_per_line;
|
||||
|
||||
/* Calibration parameters (derived from sensor geometry) */
|
||||
guint16 key_calibration_line;
|
||||
guint8 calibration_frames;
|
||||
guint8 calibration_iterations;
|
||||
|
||||
/* Factory calibration values (from factory_bits subtag 3) */
|
||||
guint8 *factory_calibration_values;
|
||||
gsize factory_calibration_values_len;
|
||||
|
||||
/* Optional factory calibration data (from factory_bits subtag 7) */
|
||||
guint8 *factory_calib_data;
|
||||
gsize factory_calib_data_len;
|
||||
|
||||
/* Accumulated calibration data (built during calibration loop) */
|
||||
guint8 *calib_data;
|
||||
gsize calib_data_len;
|
||||
|
||||
/* Whether this is a line_update_type_1 device */
|
||||
gboolean is_type1_device;
|
||||
} ValidityCaptureState;
|
||||
|
||||
/* ================================================================
|
||||
* Chunk parsing — split/merge TLV-encoded capture program
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Split a capture program binary into an array of chunks.
|
||||
* Returns an array of ValidityCaptureChunk (caller must free with
|
||||
* validity_capture_chunks_free). Sets n_chunks to the count.
|
||||
* Returns NULL on parse error.
|
||||
*/
|
||||
ValidityCaptureChunk *validity_capture_split_chunks (const guint8 *data,
|
||||
gsize data_len,
|
||||
gsize *n_chunks);
|
||||
|
||||
/*
|
||||
* Merge an array of chunks back into a binary blob.
|
||||
* Caller must g_free() the returned buffer.
|
||||
* Returns NULL on error and sets out_len to 0.
|
||||
*/
|
||||
guint8 *validity_capture_merge_chunks (const ValidityCaptureChunk *chunks,
|
||||
gsize n_chunks,
|
||||
gsize *out_len);
|
||||
|
||||
/*
|
||||
* Free an array of chunks (including each chunk's data).
|
||||
*/
|
||||
void validity_capture_chunks_free (ValidityCaptureChunk *chunks,
|
||||
gsize n_chunks);
|
||||
|
||||
/* ================================================================
|
||||
* Timeslot instruction decoder
|
||||
*
|
||||
* The timeslot table is a bytecode program for the sensor DSP.
|
||||
* Each instruction is 1-3 bytes. We need to decode them to find
|
||||
* specific instructions for patching.
|
||||
*
|
||||
* Returns: opcode in *opcode, instruction length in *insn_len,
|
||||
* operands in operands[] (up to 3), count in *n_operands.
|
||||
* Returns FALSE if the instruction cannot be decoded.
|
||||
* ================================================================ */
|
||||
gboolean validity_capture_decode_insn (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 *opcode,
|
||||
guint8 *insn_len,
|
||||
guint32 operands[3],
|
||||
guint8 *n_operands);
|
||||
|
||||
/* Timeslot instruction opcodes */
|
||||
#define TST_OP_NOOP 0
|
||||
#define TST_OP_END_OF_TABLE 1
|
||||
#define TST_OP_RETURN 2
|
||||
#define TST_OP_CLEAR_SO 3
|
||||
#define TST_OP_END_OF_DATA 4
|
||||
#define TST_OP_MACRO 5
|
||||
#define TST_OP_ENABLE_RX 6
|
||||
#define TST_OP_IDLE_RX 7
|
||||
#define TST_OP_ENABLE_SO 8
|
||||
#define TST_OP_DISABLE_SO 9
|
||||
#define TST_OP_INTERRUPT 10
|
||||
#define TST_OP_CALL 11
|
||||
#define TST_OP_FEATURES 12
|
||||
#define TST_OP_REG_WRITE 13
|
||||
#define TST_OP_SAMPLE 14
|
||||
#define TST_OP_SAMPLE_REPEAT 15
|
||||
|
||||
/*
|
||||
* Find the Nth instruction with the given opcode.
|
||||
* Returns the byte offset (pc) of the instruction, or -1 if not found.
|
||||
*/
|
||||
gssize validity_capture_find_nth_insn (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 target_opcode,
|
||||
guint n);
|
||||
|
||||
/*
|
||||
* Find the Nth Register Write instruction to a specific register address.
|
||||
* Returns the byte offset (pc), or -1 if not found.
|
||||
*/
|
||||
gssize validity_capture_find_nth_regwrite (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint32 reg_addr,
|
||||
guint n);
|
||||
|
||||
/* ================================================================
|
||||
* Timeslot table patching
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* First pass: patch the timeslot table multiplier.
|
||||
* For each "Call" instruction (opcode 0x10-0x17), if repeat > 1,
|
||||
* multiply it by mult and optionally increment the address.
|
||||
* Modifies data in-place. Returns TRUE on success.
|
||||
*/
|
||||
gboolean validity_capture_patch_timeslot_table (guint8 *data,
|
||||
gsize data_len,
|
||||
gboolean inc_address,
|
||||
guint8 mult);
|
||||
|
||||
/*
|
||||
* Second pass: find the register write to 0x8000203C in the Call
|
||||
* target subroutine and patch its value with the factory calibration
|
||||
* value at key_calibration_line.
|
||||
* Modifies data in-place. Returns TRUE on success; FALSE means
|
||||
* no matching instruction was found (non-fatal).
|
||||
*/
|
||||
gboolean validity_capture_patch_timeslot_again (guint8 *data,
|
||||
gsize data_len,
|
||||
const guint8 *factory_calibration_values,
|
||||
gsize factory_cal_len,
|
||||
guint16 key_calibration_line);
|
||||
|
||||
/* ================================================================
|
||||
* build_cmd_02 — main capture command builder
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Build a capture command (cmd 0x02).
|
||||
* Format: 0x02 | bytes_per_line(2LE) | req_lines(2LE) | merged_chunks
|
||||
*
|
||||
* The capture program is loaded from capture_state->capture_prog,
|
||||
* split into chunks, patched (timeslot, line_update), and reassembled.
|
||||
*
|
||||
* Returns a newly-allocated buffer (caller must g_free) or NULL on error.
|
||||
* Sets out_len to the buffer size.
|
||||
*/
|
||||
guint8 *validity_capture_build_cmd_02 (const ValidityCaptureState *capture,
|
||||
const ValiditySensorTypeInfo *type_info,
|
||||
ValidityCaptureMode mode,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Factory bits parsing
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Parse the raw factory bits response (from cmd 0x6f).
|
||||
* Extracts subtag 3 (factory_calibration_values) and optionally
|
||||
* subtag 7 (factory_calib_data).
|
||||
*
|
||||
* Response format (after 2-byte status, already stripped):
|
||||
* wtf(4LE) | entries(4LE) | entry[]:
|
||||
* ptr(4LE) | length(2LE) | tag(2LE) | subtag(2LE) | flags(2LE) | data(length)
|
||||
*
|
||||
* Returns TRUE if at least subtag 3 was found.
|
||||
*/
|
||||
gboolean validity_capture_parse_factory_bits (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 **cal_values,
|
||||
gsize *cal_values_len,
|
||||
guint8 **cal_data,
|
||||
gsize *cal_data_len);
|
||||
|
||||
/* ================================================================
|
||||
* Frame averaging
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Average raw multi-frame capture data from EP 0x82.
|
||||
* Deinterlaces frames and averages interleaved lines.
|
||||
*
|
||||
* Returns a newly-allocated buffer (one frame), or NULL on error.
|
||||
* The caller must g_free() the buffer.
|
||||
*/
|
||||
guint8 *validity_capture_average_frames (const guint8 *raw_data,
|
||||
gsize raw_len,
|
||||
guint16 lines_per_frame,
|
||||
guint16 bytes_per_line,
|
||||
guint16 lines_per_calibration_data,
|
||||
guint8 calibration_frames,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Calibration data processing
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Process averaged calibration frame into calibration data.
|
||||
* Applies scaling and accumulates with existing calib_data.
|
||||
* Updates calib_data/calib_data_len in place.
|
||||
*
|
||||
* If calib_data is NULL, initializes from the frame.
|
||||
* If non-NULL, combines (adds signed values, clips).
|
||||
*/
|
||||
void validity_capture_process_calibration (guint8 **calib_data,
|
||||
gsize *calib_data_len,
|
||||
const guint8 *averaged_frame,
|
||||
gsize frame_len,
|
||||
guint16 bytes_per_line);
|
||||
|
||||
/*
|
||||
* Build the clean slate format for flash persistence.
|
||||
* Format: magic(2LE=0x5002) | inner_len(2LE) | sha256(32) | zeroes(32) |
|
||||
* data_len(2LE) | data | trailing_zero(2LE=0)
|
||||
*
|
||||
* Returns a newly-allocated buffer or NULL on error.
|
||||
*/
|
||||
guint8 *validity_capture_build_clean_slate (const guint8 *averaged_frame,
|
||||
gsize frame_len,
|
||||
gsize *out_len);
|
||||
|
||||
/*
|
||||
* Verify clean slate format (check magic and SHA256 hash).
|
||||
* Returns TRUE if the format is valid.
|
||||
*/
|
||||
gboolean validity_capture_verify_clean_slate (const guint8 *data,
|
||||
gsize data_len);
|
||||
|
||||
/* ================================================================
|
||||
* Bitpack — compress factory calibration values for Line Update
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Pack calibration values using minimum-bit encoding.
|
||||
* Returns packed data, minimum value (v1), bit count (v0),
|
||||
* and packed length.
|
||||
*
|
||||
* Python-validity bitpack(): find min, max delta, encode each value
|
||||
* as (value - min) in minimum bits, pack as little-endian.
|
||||
*
|
||||
* Returns a newly-allocated buffer or NULL on error.
|
||||
*/
|
||||
guint8 *validity_capture_bitpack (const guint8 *values,
|
||||
gsize values_len,
|
||||
guint8 *out_v0,
|
||||
guint8 *out_v1,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Finger ID mapping
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Map FpFinger enum value to VCSFW finger subtype (1-10).
|
||||
* Returns 0 if the finger is not recognized.
|
||||
*/
|
||||
guint16 validity_finger_to_subtype (guint finger);
|
||||
|
||||
/*
|
||||
* Map VCSFW finger subtype (1-10) to FpFinger enum value.
|
||||
* Returns -1 if the subtype is not recognized.
|
||||
*/
|
||||
gint validity_subtype_to_finger (guint16 subtype);
|
||||
|
||||
/* ================================================================
|
||||
* LED control commands — for user feedback during capture
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Build the LED "glow start scan" command (sent via TLS app).
|
||||
* Returns a static buffer and sets out_len.
|
||||
*/
|
||||
const guint8 *validity_capture_glow_start_cmd (gsize *out_len);
|
||||
|
||||
/*
|
||||
* Build the LED "glow end scan" command (sent via TLS app).
|
||||
* Returns a static buffer and sets out_len.
|
||||
*/
|
||||
const guint8 *validity_capture_glow_end_cmd (gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* CaptureProg table lookup
|
||||
* ================================================================ */
|
||||
|
||||
/*
|
||||
* Look up the capture program for a given ROM version and sensor type.
|
||||
* Returns a pointer to the static blob data and sets out_len.
|
||||
* Returns NULL if no matching entry is found.
|
||||
*/
|
||||
const guint8 *validity_capture_prog_lookup (guint8 rom_major,
|
||||
guint8 rom_minor,
|
||||
guint16 dev_type,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Capture state lifecycle
|
||||
* ================================================================ */
|
||||
|
||||
void validity_capture_state_init (ValidityCaptureState *state);
|
||||
void validity_capture_state_clear (ValidityCaptureState *state);
|
||||
|
||||
/*
|
||||
* Initialize capture state from sensor info and factory bits.
|
||||
* Loads the CaptureProg, computes geometry, sets calibration params.
|
||||
* Returns TRUE on success.
|
||||
*/
|
||||
gboolean validity_capture_state_setup (ValidityCaptureState *state,
|
||||
const ValiditySensorTypeInfo *type_info,
|
||||
guint16 dev_type,
|
||||
guint8 rom_major,
|
||||
guint8 rom_minor,
|
||||
const guint8 *factory_bits,
|
||||
gsize factory_bits_len);
|
||||
|
||||
/* Chunk type IDs used in capture programs */
|
||||
#define CAPT_CHUNK_REPLY_CONFIG 0x0017
|
||||
#define CAPT_CHUNK_FINGER_DETECT 0x0026
|
||||
#define CAPT_CHUNK_IMAGE_RECON 0x002e
|
||||
#define CAPT_CHUNK_2D_PARAMS 0x002f
|
||||
#define CAPT_CHUNK_LINE_UPDATE 0x0030
|
||||
#define CAPT_CHUNK_TIMESLOT_2D 0x0034
|
||||
#define CAPT_CHUNK_TS_OFFSET 0x0029
|
||||
#define CAPT_CHUNK_TS_FD_OFFSET 0x0035
|
||||
#define CAPT_CHUNK_LINE_UPDATE_XFORM 0x0043
|
||||
#define CAPT_CHUNK_INTERLEAVE 0x0044
|
||||
#define CAPT_CHUNK_WTF 0x004e
|
||||
|
||||
/* Capture program cookie for the ACM Config chunk */
|
||||
#define CAPT_CHUNK_ACM_CONFIG 0x002a
|
||||
#define CAPT_CHUNK_CEM_CONFIG 0x002c
|
||||
294
libfprint/drivers/validity/validity_data.c
Normal file
294
libfprint/drivers/validity/validity_data.c
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Runtime data file loader for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "validity_data.h"
|
||||
#include "fpi-log.h"
|
||||
#include "fp-device.h"
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Search paths for data files */
|
||||
const gchar *validity_data_search_paths[] = {
|
||||
"/usr/share/libfprint/validity",
|
||||
"/usr/local/share/libfprint/validity",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* HMAC-SHA256 key for integrity verification.
|
||||
* This is NOT secret — it is also compiled into the data package generator.
|
||||
* Purpose: detect file corruption and casual tampering, not authentication.
|
||||
* Plain text: "libfprint-validity-data-integrit" (32 bytes) */
|
||||
static const guint8 hmac_key[32] = {
|
||||
0x6c, 0x69, 0x62, 0x66, 0x70, 0x72, 0x69, 0x6e, /* "libfprin" */
|
||||
0x74, 0x2d, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x69, /* "t-validi" */
|
||||
0x74, 0x79, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x2d, /* "ty-data-" */
|
||||
0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, /* "integrit" */
|
||||
};
|
||||
|
||||
/* Per-device blob filename mapping (indexed by tag) */
|
||||
static const gchar *device_blob_filenames[] = {
|
||||
[VALIDITY_DATA_INIT] = "init.bin",
|
||||
[VALIDITY_DATA_INIT_CLEAN_SLATE] = "init_clean_slate.bin",
|
||||
[VALIDITY_DATA_RESET] = "reset.bin",
|
||||
[VALIDITY_DATA_DB_WRITE_ENABLE] = "db_write_enable.bin",
|
||||
};
|
||||
|
||||
/* Common data filename mapping (indexed by tag) */
|
||||
static const struct
|
||||
{
|
||||
ValidityDataTag tag;
|
||||
const gchar *filename;
|
||||
} common_file_map[] = {
|
||||
{ VALIDITY_DATA_PARTITION_SIG_STANDARD, "partition_sig_standard.bin" },
|
||||
{ VALIDITY_DATA_PARTITION_SIG_0090, "partition_sig_0090.bin" },
|
||||
{ VALIDITY_DATA_CA_PUBKEY, "ca_pubkey.bin" },
|
||||
{ VALIDITY_DATA_TLS_PASSWORD, "tls_password.bin" },
|
||||
{ VALIDITY_DATA_GWK_SIGN, "gwk_sign.bin" },
|
||||
{ VALIDITY_DATA_FW_PUBKEY_X, "fw_pubkey_x.bin" },
|
||||
{ VALIDITY_DATA_FW_PUBKEY_Y, "fw_pubkey_y.bin" },
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* HMAC-SHA256 verification
|
||||
* ================================================================ */
|
||||
|
||||
static gboolean
|
||||
verify_hmac (const guint8 *data,
|
||||
gsize data_len,
|
||||
const guint8 *expected_hmac)
|
||||
{
|
||||
guint8 computed[32];
|
||||
|
||||
GHmac *hmac = g_hmac_new (G_CHECKSUM_SHA256, hmac_key, sizeof (hmac_key));
|
||||
|
||||
g_hmac_update (hmac, data, data_len);
|
||||
|
||||
gsize digest_len = sizeof (computed);
|
||||
g_hmac_get_digest (hmac, computed, &digest_len);
|
||||
g_hmac_unref (hmac);
|
||||
|
||||
/* Constant-time comparison */
|
||||
guint diff = 0;
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
diff |= computed[i] ^ expected_hmac[i];
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Public API
|
||||
* ================================================================ */
|
||||
|
||||
void
|
||||
validity_data_store_init (ValidityDataStore *store)
|
||||
{
|
||||
memset (store, 0, sizeof (*store));
|
||||
}
|
||||
|
||||
void
|
||||
validity_data_store_free (ValidityDataStore *store)
|
||||
{
|
||||
for (gsize i = 0; i < VALIDITY_DATA_NUM_TAGS; i++)
|
||||
g_clear_pointer (&store->entries[i], g_bytes_unref);
|
||||
}
|
||||
|
||||
gboolean
|
||||
validity_data_load_file (ValidityDataStore *store,
|
||||
ValidityDataTag tag,
|
||||
const gchar *filepath,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree guint8 *contents = NULL;
|
||||
gsize length = 0;
|
||||
|
||||
g_return_val_if_fail (store != NULL, FALSE);
|
||||
g_return_val_if_fail (tag < VALIDITY_DATA_NUM_TAGS, FALSE);
|
||||
g_return_val_if_fail (filepath != NULL, FALSE);
|
||||
|
||||
if (!g_file_get_contents (filepath, (gchar **) &contents, &length, error))
|
||||
return FALSE;
|
||||
|
||||
if (length < VALIDITY_DATA_MIN_FILE_SIZE)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Data file '%s' too short (%zu bytes, minimum %d)",
|
||||
filepath, length, VALIDITY_DATA_MIN_FILE_SIZE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gsize data_len = length - VALIDITY_DATA_HMAC_SIZE;
|
||||
const guint8 *hmac_trailer = contents + data_len;
|
||||
|
||||
if (!verify_hmac (contents, data_len, hmac_trailer))
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Data file '%s' failed HMAC verification — "
|
||||
"file may be corrupt or from an incompatible version",
|
||||
filepath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Store only the data portion (without HMAC trailer) */
|
||||
g_clear_pointer (&store->entries[tag], g_bytes_unref);
|
||||
store->entries[tag] = g_bytes_new (contents, data_len);
|
||||
|
||||
fp_dbg ("Loaded data file: %s (%zu bytes)", filepath, data_len);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
validity_data_load_device (ValidityDataStore *store,
|
||||
guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree gchar *subdir = g_strdup_printf ("%04x_%04x", vid, pid);
|
||||
gboolean found_any = FALSE;
|
||||
|
||||
g_return_val_if_fail (store != NULL, FALSE);
|
||||
|
||||
for (const gchar **base = validity_data_search_paths; *base != NULL; base++)
|
||||
{
|
||||
g_autofree gchar *dir_path = g_build_filename (*base, subdir, NULL);
|
||||
|
||||
if (!g_file_test (dir_path, G_FILE_TEST_IS_DIR))
|
||||
continue;
|
||||
|
||||
found_any = TRUE;
|
||||
fp_info ("Loading device data from %s", dir_path);
|
||||
|
||||
for (gsize i = 0; i < G_N_ELEMENTS (device_blob_filenames); i++)
|
||||
{
|
||||
if (device_blob_filenames[i] == NULL)
|
||||
continue;
|
||||
|
||||
g_autofree gchar *fpath =
|
||||
g_build_filename (dir_path, device_blob_filenames[i], NULL);
|
||||
|
||||
if (!g_file_test (fpath, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
fp_dbg ("Optional device blob not found: %s", fpath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!validity_data_load_file (store, (ValidityDataTag) i,
|
||||
fpath, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
break; /* Found directory — don't search further */
|
||||
}
|
||||
|
||||
if (!found_any)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||
"Device data files not found for %04x:%04x. "
|
||||
"Install the libfprint-validity-data package. "
|
||||
"Search paths: /usr/share/libfprint/validity/, "
|
||||
"/usr/local/share/libfprint/validity/",
|
||||
vid, pid);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Verify that init.bin was loaded (it is mandatory) */
|
||||
if (!store->entries[VALIDITY_DATA_INIT])
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||
"Required file init.bin not found for %04x:%04x",
|
||||
vid, pid);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
validity_data_load_common (ValidityDataStore *store,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (store != NULL, FALSE);
|
||||
|
||||
for (gsize i = 0; i < G_N_ELEMENTS (common_file_map); i++)
|
||||
{
|
||||
ValidityDataTag tag = common_file_map[i].tag;
|
||||
const gchar *filename = common_file_map[i].filename;
|
||||
gboolean found = FALSE;
|
||||
|
||||
for (const gchar **base = validity_data_search_paths;
|
||||
*base != NULL; base++)
|
||||
{
|
||||
g_autofree gchar *fpath =
|
||||
g_build_filename (*base, filename, NULL);
|
||||
|
||||
if (!g_file_test (fpath, G_FILE_TEST_IS_REGULAR))
|
||||
continue;
|
||||
|
||||
if (!validity_data_load_file (store, tag, fpath, error))
|
||||
return FALSE;
|
||||
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR,
|
||||
FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||
"Common data file '%s' not found. "
|
||||
"Install the libfprint-validity-data package. "
|
||||
"Search paths: /usr/share/libfprint/validity/, "
|
||||
"/usr/local/share/libfprint/validity/",
|
||||
filename);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GBytes *
|
||||
validity_data_get (const ValidityDataStore *store,
|
||||
ValidityDataTag tag)
|
||||
{
|
||||
g_return_val_if_fail (store != NULL, NULL);
|
||||
g_return_val_if_fail (tag < VALIDITY_DATA_NUM_TAGS, NULL);
|
||||
|
||||
return store->entries[tag];
|
||||
}
|
||||
|
||||
const guint8 *
|
||||
validity_data_get_bytes (const ValidityDataStore *store,
|
||||
ValidityDataTag tag,
|
||||
gsize *len)
|
||||
{
|
||||
g_return_val_if_fail (len != NULL, NULL);
|
||||
|
||||
GBytes *bytes = validity_data_get (store, tag);
|
||||
if (!bytes)
|
||||
{
|
||||
*len = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_bytes_get_data (bytes, len);
|
||||
}
|
||||
102
libfprint/drivers/validity/validity_data.h
Normal file
102
libfprint/drivers/validity/validity_data.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Runtime data file loader for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Loads per-device blob data and common pairing/TLS constants from
|
||||
* external .bin files installed by the libfprint-validity-data package.
|
||||
*
|
||||
* File format: [raw_data: N bytes][HMAC-SHA256: 32 bytes]
|
||||
* Install path: /usr/share/libfprint/validity/
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/* HMAC-SHA256 trailer size appended to each .bin file */
|
||||
#define VALIDITY_DATA_HMAC_SIZE 32
|
||||
|
||||
/* Minimum file size: at least 1 byte of data + 32 bytes HMAC */
|
||||
#define VALIDITY_DATA_MIN_FILE_SIZE (1 + VALIDITY_DATA_HMAC_SIZE)
|
||||
|
||||
/* Data file tags — identifies which blob is being loaded */
|
||||
typedef enum {
|
||||
/* Per-device blobs (in <vid>_<pid>/ subdirectory) */
|
||||
VALIDITY_DATA_INIT = 0,
|
||||
VALIDITY_DATA_INIT_CLEAN_SLATE,
|
||||
VALIDITY_DATA_RESET,
|
||||
VALIDITY_DATA_DB_WRITE_ENABLE,
|
||||
|
||||
/* Common data files (in base directory) */
|
||||
VALIDITY_DATA_PARTITION_SIG_STANDARD,
|
||||
VALIDITY_DATA_PARTITION_SIG_0090,
|
||||
VALIDITY_DATA_CA_PUBKEY,
|
||||
VALIDITY_DATA_TLS_PASSWORD,
|
||||
VALIDITY_DATA_GWK_SIGN,
|
||||
VALIDITY_DATA_FW_PUBKEY_X,
|
||||
VALIDITY_DATA_FW_PUBKEY_Y,
|
||||
|
||||
VALIDITY_DATA_NUM_TAGS,
|
||||
} ValidityDataTag;
|
||||
|
||||
/* Container for loaded data files */
|
||||
typedef struct
|
||||
{
|
||||
GBytes *entries[VALIDITY_DATA_NUM_TAGS];
|
||||
} ValidityDataStore;
|
||||
|
||||
/* Search paths for data files (defined in validity_data.c) */
|
||||
extern const gchar *validity_data_search_paths[];
|
||||
|
||||
/* Initialize a data store (all entries NULL) */
|
||||
void validity_data_store_init (ValidityDataStore *store);
|
||||
|
||||
/* Free all loaded data in a store */
|
||||
void validity_data_store_free (ValidityDataStore *store);
|
||||
|
||||
/* Load a single .bin file, verify its HMAC, and store in the given tag slot.
|
||||
* Returns TRUE on success. On failure, sets error and returns FALSE. */
|
||||
gboolean validity_data_load_file (ValidityDataStore *store,
|
||||
ValidityDataTag tag,
|
||||
const gchar *filepath,
|
||||
GError **error);
|
||||
|
||||
/* Load all per-device blob files for the given VID/PID.
|
||||
* Searches validity_data_search_paths for <vid>_<pid>/ subdirectory.
|
||||
* Returns TRUE if all required blobs found, FALSE with error otherwise. */
|
||||
gboolean validity_data_load_device (ValidityDataStore *store,
|
||||
guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error);
|
||||
|
||||
/* Load all common data files (partition sigs, CA cert, TLS keys, etc.).
|
||||
* Returns TRUE if all files found, FALSE with error otherwise. */
|
||||
gboolean validity_data_load_common (ValidityDataStore *store,
|
||||
GError **error);
|
||||
|
||||
/* Get the raw data bytes for a given tag (without HMAC trailer).
|
||||
* Returns NULL if the tag has not been loaded. The returned GBytes
|
||||
* is owned by the store — do not unref. */
|
||||
GBytes *validity_data_get (const ValidityDataStore *store,
|
||||
ValidityDataTag tag);
|
||||
|
||||
/* Convenience: get raw data pointer and length for a tag.
|
||||
* Returns NULL with *len=0 if not loaded. */
|
||||
const guint8 *validity_data_get_bytes (const ValidityDataStore *store,
|
||||
ValidityDataTag tag,
|
||||
gsize *len);
|
||||
571
libfprint/drivers/validity/validity_db.c
Normal file
571
libfprint/drivers/validity/validity_db.c
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
* Database operations for Validity/Synaptics VCSFW sensors
|
||||
*
|
||||
* Implements on-chip template database management: command builders,
|
||||
* response parsers, identity encoding, and finger data formatting.
|
||||
*
|
||||
* Reference: python-validity db.py, flash.py, sensor.py
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "drivers_api.h"
|
||||
#include "validity_pack.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "validity_db.h"
|
||||
#include "validity.h"
|
||||
#include "validity_hal.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* Structure cleanup helpers
|
||||
* ================================================================ */
|
||||
|
||||
void
|
||||
validity_db_info_clear (ValidityDbInfo *info)
|
||||
{
|
||||
g_clear_pointer (&info->roots, g_free);
|
||||
memset (info, 0, sizeof (*info));
|
||||
}
|
||||
|
||||
void
|
||||
validity_user_storage_clear (ValidityUserStorage *storage)
|
||||
{
|
||||
g_clear_pointer (&storage->name, g_free);
|
||||
g_clear_pointer (&storage->user_dbids, g_free);
|
||||
g_clear_pointer (&storage->user_val_sizes, g_free);
|
||||
memset (storage, 0, sizeof (*storage));
|
||||
}
|
||||
|
||||
void
|
||||
validity_match_result_clear (ValidityMatchResult *result)
|
||||
{
|
||||
g_clear_pointer (&result->hash, g_free);
|
||||
memset (result, 0, sizeof (*result));
|
||||
}
|
||||
|
||||
void
|
||||
validity_user_clear (ValidityUser *user)
|
||||
{
|
||||
g_clear_pointer (&user->identity, g_free);
|
||||
g_clear_pointer (&user->fingers, g_free);
|
||||
memset (user, 0, sizeof (*user));
|
||||
}
|
||||
|
||||
void
|
||||
validity_db_record_clear (ValidityDbRecord *record)
|
||||
{
|
||||
g_clear_pointer (&record->value, g_free);
|
||||
memset (record, 0, sizeof (*record));
|
||||
}
|
||||
|
||||
void
|
||||
validity_record_children_clear (ValidityRecordChildren *children)
|
||||
{
|
||||
g_clear_pointer (&children->children, g_free);
|
||||
memset (children, 0, sizeof (*children));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Command builders
|
||||
*
|
||||
* Each function allocates and returns a binary command payload.
|
||||
* The caller must g_free() the result.
|
||||
* ================================================================ */
|
||||
|
||||
/* cmd 0x4B: Get user storage
|
||||
* Format: 0x4B | dbid(2LE) | name_len(2LE) | name(NUL-terminated) */
|
||||
guint8 *
|
||||
validity_db_build_cmd_get_user_storage (const gchar *name,
|
||||
gsize *out_len)
|
||||
{
|
||||
gsize name_len = 0;
|
||||
|
||||
if (name && name[0] != '\0')
|
||||
name_len = strlen (name) + 1; /* include NUL terminator */
|
||||
|
||||
return validity_pack_new (out_len, "bhhd",
|
||||
VCSFW_CMD_GET_USER_STORAGE,
|
||||
0, /* dbid = 0 (lookup by name) */
|
||||
(int) name_len,
|
||||
(const guint8 *) name, name_len);
|
||||
}
|
||||
|
||||
/* cmd 0x4A: Get user by dbid
|
||||
* Format: 0x4A | dbid(2LE) | 0(2LE) | 0(2LE) */
|
||||
guint8 *
|
||||
validity_db_build_cmd_get_user (guint16 dbid,
|
||||
gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "bhhh",
|
||||
VCSFW_CMD_GET_USER, dbid, 0, 0);
|
||||
}
|
||||
|
||||
/* cmd 0x47: New record
|
||||
* Format: 0x47 | parent(2LE) | type(2LE) | storage(2LE) | data_len(2LE) | data */
|
||||
guint8 *
|
||||
validity_db_build_cmd_new_record (guint16 parent,
|
||||
guint16 type,
|
||||
guint16 storage,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "bhhhhd",
|
||||
VCSFW_CMD_NEW_RECORD,
|
||||
(int) parent, (int) type,
|
||||
(int) storage, (int) data_len,
|
||||
data, data_len);
|
||||
}
|
||||
|
||||
/* cmd 0x48: Delete record
|
||||
* Format: 0x48 | dbid(2LE) */
|
||||
guint8 *
|
||||
validity_db_build_cmd_del_record (guint16 dbid,
|
||||
gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "bh",
|
||||
VCSFW_CMD_DEL_RECORD, dbid);
|
||||
}
|
||||
|
||||
/* cmd 0x1a: Call cleanups (commit pending writes) */
|
||||
guint8 *
|
||||
validity_db_build_cmd_call_cleanups (gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "b", 0x1a);
|
||||
}
|
||||
|
||||
/* cmd 0x69: Create enrollment / End enrollment
|
||||
* Format: 0x69 | flag(4LE)
|
||||
* start=TRUE: flag=1, start=FALSE: flag=0 */
|
||||
guint8 *
|
||||
validity_db_build_cmd_create_enrollment (gboolean start,
|
||||
gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "bw",
|
||||
VCSFW_CMD_CREATE_ENROLLMENT,
|
||||
(guint32) (start ? 1 : 0));
|
||||
}
|
||||
|
||||
/* cmd 0x6B: Enrollment update (with template data)
|
||||
* Format: 0x6B | prev_data */
|
||||
guint8 *
|
||||
validity_db_build_cmd_enrollment_update (const guint8 *prev_data,
|
||||
gsize prev_len,
|
||||
gsize *out_len)
|
||||
{
|
||||
return validity_pack_new (out_len, "bd",
|
||||
VCSFW_CMD_ENROLLMENT_UPDATE,
|
||||
prev_data, prev_len);
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* Response parsers
|
||||
*
|
||||
* All data parameters point PAST the 2-byte status.
|
||||
* ================================================================ */
|
||||
|
||||
/* Parse DB info response (cmd 0x45) */
|
||||
gboolean
|
||||
validity_db_parse_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityDbInfo *out)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
memset (out, 0, sizeof (*out));
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
/* Header: unknown1(4LE) unknown0(4LE) total(4LE) used(4LE) free(4LE) records(2LE) nroots(2LE) = 0x18 bytes */
|
||||
if (data_len < 0x18)
|
||||
return FALSE;
|
||||
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &out->unknown1))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &out->unknown0))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &out->total))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &out->used))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &out->free_space))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->records))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->n_roots))
|
||||
return FALSE;
|
||||
|
||||
/* Root record IDs */
|
||||
if (out->n_roots > 0)
|
||||
{
|
||||
out->roots = g_new0 (guint16, out->n_roots);
|
||||
for (guint16 i = 0; i < out->n_roots; i++)
|
||||
{
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->roots[i]))
|
||||
{
|
||||
validity_db_info_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Parse user storage response (cmd 0x4B)
|
||||
* Format: recid(2LE) usercnt(2LE) namesz(2LE) unknown(2LE)
|
||||
* usrtab[usercnt * 4 bytes] name[namesz bytes] */
|
||||
gboolean
|
||||
validity_db_parse_user_storage (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityUserStorage *out)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
guint16 name_sz, unknown;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
memset (out, 0, sizeof (*out));
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
/* Header: 8 bytes */
|
||||
if (data_len < 8)
|
||||
return FALSE;
|
||||
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->dbid))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->user_count))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &name_sz))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &unknown))
|
||||
return FALSE;
|
||||
|
||||
/* User table: 4 bytes per entry (dbid(2LE) + value_size(2LE)) */
|
||||
if (out->user_count > 0)
|
||||
{
|
||||
out->user_dbids = g_new0 (guint16, out->user_count);
|
||||
out->user_val_sizes = g_new0 (guint16, out->user_count);
|
||||
|
||||
for (guint16 i = 0; i < out->user_count; i++)
|
||||
{
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->user_dbids[i]) ||
|
||||
!fpi_byte_reader_get_uint16_le (&reader, &out->user_val_sizes[i]))
|
||||
{
|
||||
validity_user_storage_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Name */
|
||||
if (name_sz > 0)
|
||||
{
|
||||
const guint8 *name_data;
|
||||
if (!fpi_byte_reader_get_data (&reader, name_sz, &name_data))
|
||||
{
|
||||
validity_user_storage_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
out->name = g_strndup ((const gchar *) name_data, name_sz);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Parse user response (cmd 0x4A)
|
||||
* Format: recid(2LE) fingercnt(2LE) unknown(2LE) identitysz(2LE)
|
||||
* fingertab[8 * fingercnt] identity[identitysz] */
|
||||
gboolean
|
||||
validity_db_parse_user (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityUser *out)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
guint16 unknown, identity_sz;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
memset (out, 0, sizeof (*out));
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
/* Header: 8 bytes */
|
||||
if (data_len < 8)
|
||||
return FALSE;
|
||||
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->dbid))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->finger_count))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &unknown))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &identity_sz))
|
||||
return FALSE;
|
||||
|
||||
/* Finger table: 8 bytes per entry */
|
||||
if (out->finger_count > 0)
|
||||
{
|
||||
out->fingers = g_new0 (ValidityFingerEntry, out->finger_count);
|
||||
for (guint16 i = 0; i < out->finger_count; i++)
|
||||
{
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->fingers[i].dbid) ||
|
||||
!fpi_byte_reader_get_uint16_le (&reader, &out->fingers[i].subtype) ||
|
||||
!fpi_byte_reader_get_uint16_le (&reader, &out->fingers[i].storage) ||
|
||||
!fpi_byte_reader_get_uint16_le (&reader, &out->fingers[i].value_size))
|
||||
{
|
||||
validity_user_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Identity bytes */
|
||||
if (identity_sz > 0)
|
||||
{
|
||||
const guint8 *id_data;
|
||||
if (!fpi_byte_reader_get_data (&reader, identity_sz, &id_data))
|
||||
{
|
||||
validity_user_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
out->identity = g_memdup2 (id_data, identity_sz);
|
||||
out->identity_len = identity_sz;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Parse record value response (cmd 0x49)
|
||||
* Format: dbid(2LE) type(2LE) storage(2LE) sz(2LE) pad(2bytes) value[sz] */
|
||||
gboolean
|
||||
validity_db_parse_record_value (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityDbRecord *out)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
guint16 sz;
|
||||
guint16 pad;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
memset (out, 0, sizeof (*out));
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
/* Header: 10 bytes (python-validity: unpack('<xxHHHHxx', rsp[:12])
|
||||
* but our data is already past status, so: HHHHxx = 10 bytes) */
|
||||
if (data_len < 10)
|
||||
return FALSE;
|
||||
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->dbid))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->type))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->storage))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &sz))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &pad))
|
||||
return FALSE;
|
||||
|
||||
/* Value data */
|
||||
if (sz > 0)
|
||||
{
|
||||
const guint8 *val_data;
|
||||
if (!fpi_byte_reader_get_data (&reader, sz, &val_data))
|
||||
{
|
||||
validity_db_record_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
out->value = g_memdup2 (val_data, sz);
|
||||
out->value_len = sz;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Parse record children response (cmd 0x46)
|
||||
* Format: dbid(2LE) type(2LE) storage(2LE) sz(2LE) cnt(2LE) pad(2bytes)
|
||||
* children[cnt * 4 bytes: dbid(2LE) type(2LE)] */
|
||||
gboolean
|
||||
validity_db_parse_record_children (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityRecordChildren *out)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
guint16 sz, pad;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
memset (out, 0, sizeof (*out));
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
/* Header: 12 bytes (python-validity: unpack('<xxHHHHHxx', rsp[:14])
|
||||
* past status: HHHH H xx = 12 bytes) */
|
||||
if (data_len < 12)
|
||||
return FALSE;
|
||||
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->dbid))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->type))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->storage))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &sz))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->child_count))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &pad))
|
||||
return FALSE;
|
||||
|
||||
/* Child entries */
|
||||
if (out->child_count > 0)
|
||||
{
|
||||
out->children = g_new0 (ValidityRecordChild, out->child_count);
|
||||
for (guint16 i = 0; i < out->child_count; i++)
|
||||
{
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->children[i].dbid) ||
|
||||
!fpi_byte_reader_get_uint16_le (&reader, &out->children[i].type))
|
||||
{
|
||||
validity_record_children_clear (out);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Parse new record response (cmd 0x47)
|
||||
* Format: record_id(2LE) */
|
||||
gboolean
|
||||
validity_db_parse_new_record_id (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 *out_record_id)
|
||||
{
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out_record_id != NULL, FALSE);
|
||||
|
||||
return validity_unpack (data, data_len, "h", out_record_id);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Identity helpers
|
||||
* ================================================================ */
|
||||
|
||||
/* Build a UUID-based identity for the sensor DB.
|
||||
*
|
||||
* python-validity uses Windows SIDs. For libfprint we use a UUID
|
||||
* encoded as a type-3 (SID-type) identity with zero-padding to
|
||||
* the Windows union minimum size of 0x4C bytes.
|
||||
*
|
||||
* Format: type(4LE) | len(4LE) | uuid_bytes(len) | padding */
|
||||
guint8 *
|
||||
validity_db_build_identity (const gchar *uuid_str,
|
||||
gsize *out_len)
|
||||
{
|
||||
FpiByteWriter writer;
|
||||
gsize uuid_len;
|
||||
gsize payload_len;
|
||||
gsize total_len;
|
||||
|
||||
g_return_val_if_fail (uuid_str != NULL, NULL);
|
||||
|
||||
uuid_len = strlen (uuid_str);
|
||||
payload_len = 4 + 4 + uuid_len;
|
||||
total_len = MAX (payload_len, VALIDITY_IDENTITY_MIN_SIZE);
|
||||
|
||||
fpi_byte_writer_init_with_size (&writer, total_len, FALSE);
|
||||
fpi_byte_writer_put_uint32_le (&writer, VALIDITY_IDENTITY_TYPE_SID);
|
||||
fpi_byte_writer_put_uint32_le (&writer, uuid_len);
|
||||
fpi_byte_writer_put_data (&writer, (const guint8 *) uuid_str, uuid_len);
|
||||
/* Pad to minimum identity size */
|
||||
if (total_len > payload_len)
|
||||
fpi_byte_writer_fill (&writer, 0, total_len - payload_len);
|
||||
|
||||
*out_len = fpi_byte_writer_get_pos (&writer);
|
||||
return fpi_byte_writer_reset_and_get_data (&writer);
|
||||
}
|
||||
|
||||
/* Build finger data for new_finger (from python-validity make_finger_data)
|
||||
*
|
||||
* Format:
|
||||
* subtype(2LE) | flags=3(2LE) | tinfo_len(2LE) | 0x20(2LE)
|
||||
* tag1=1(2LE) | len(2LE) | template_data
|
||||
* tag2=2(2LE) | len(2LE) | tid
|
||||
* padding[0x20 bytes] */
|
||||
guint8 *
|
||||
validity_db_build_finger_data (guint16 subtype,
|
||||
const guint8 *template_data,
|
||||
gsize template_len,
|
||||
const guint8 *tid,
|
||||
gsize tid_len,
|
||||
gsize *out_len)
|
||||
{
|
||||
FpiByteWriter writer;
|
||||
gsize template_block = 4 + template_len; /* tag(2) + len(2) + data */
|
||||
gsize tid_block = 4 + tid_len;
|
||||
gsize tinfo_len = template_block + tid_block;
|
||||
gsize total = 8 + tinfo_len + 0x20; /* header + data + padding */
|
||||
|
||||
fpi_byte_writer_init_with_size (&writer, total, FALSE);
|
||||
|
||||
/* Header */
|
||||
fpi_byte_writer_put_uint16_le (&writer, subtype);
|
||||
fpi_byte_writer_put_uint16_le (&writer, 3); /* flags */
|
||||
fpi_byte_writer_put_uint16_le (&writer, tinfo_len);
|
||||
fpi_byte_writer_put_uint16_le (&writer, 0x20);
|
||||
|
||||
/* Template block */
|
||||
fpi_byte_writer_put_uint16_le (&writer, 1); /* tag */
|
||||
fpi_byte_writer_put_uint16_le (&writer, template_len);
|
||||
if (template_len > 0)
|
||||
fpi_byte_writer_put_data (&writer, template_data, template_len);
|
||||
|
||||
/* TID block */
|
||||
fpi_byte_writer_put_uint16_le (&writer, 2); /* tag */
|
||||
fpi_byte_writer_put_uint16_le (&writer, tid_len);
|
||||
if (tid_len > 0)
|
||||
fpi_byte_writer_put_data (&writer, tid, tid_len);
|
||||
|
||||
/* Padding */
|
||||
fpi_byte_writer_fill (&writer, 0, 0x20);
|
||||
|
||||
*out_len = fpi_byte_writer_get_pos (&writer);
|
||||
return fpi_byte_writer_reset_and_get_data (&writer);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* db_write_enable blob access
|
||||
* ================================================================ */
|
||||
|
||||
const guint8 *
|
||||
validity_db_get_write_enable_blob (FpiDeviceValidity *self, gsize *out_len)
|
||||
{
|
||||
return validity_data_get_bytes (&self->device_data,
|
||||
VALIDITY_DATA_DB_WRITE_ENABLE,
|
||||
out_len);
|
||||
}
|
||||
269
libfprint/drivers/validity/validity_db.h
Normal file
269
libfprint/drivers/validity/validity_db.h
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* Database operations for Validity/Synaptics VCSFW sensors
|
||||
*
|
||||
* Implements on-chip template database management: listing users,
|
||||
* storing/deleting fingerprint records, and db_write_enable.
|
||||
*
|
||||
* The sensor has a hierarchical record DB stored on flash partition 4:
|
||||
* Storage → User → Finger → Data
|
||||
*
|
||||
* Reference: python-validity db.py, flash.py
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/* ================================================================
|
||||
* Database record types (from python-validity observation)
|
||||
* ================================================================ */
|
||||
#define VALIDITY_DB_RECORD_TYPE_STORAGE 4
|
||||
#define VALIDITY_DB_RECORD_TYPE_USER 5
|
||||
#define VALIDITY_DB_RECORD_TYPE_FINGER 6
|
||||
#define VALIDITY_DB_RECORD_TYPE_DATA 8
|
||||
|
||||
/* Identity type used in record payloads */
|
||||
#define VALIDITY_IDENTITY_TYPE_SID 3
|
||||
|
||||
/* Minimum size for encoded identity (Windows union minimum = 0x4c) */
|
||||
#define VALIDITY_IDENTITY_MIN_SIZE 0x4C
|
||||
|
||||
/* Storage name used by the sensor's DB */
|
||||
#define VALIDITY_STORAGE_NAME "StgWindsor"
|
||||
|
||||
/* Flash partition for calibration data */
|
||||
#define VALIDITY_FLASH_CALIBRATION_PARTITION 6
|
||||
|
||||
/* Clean slate flash header magic */
|
||||
#define VALIDITY_CLEAN_SLATE_MAGIC 0x5002
|
||||
|
||||
/* ================================================================
|
||||
* DB Info — returned by cmd 0x45
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint32 unknown1; /* Always 1 */
|
||||
guint32 unknown0; /* Always 0 */
|
||||
guint32 total; /* Partition size */
|
||||
guint32 used; /* Used (not deleted) */
|
||||
guint32 free_space; /* Unallocated space */
|
||||
guint16 records; /* Total number, including deleted */
|
||||
guint16 n_roots; /* Number of root records */
|
||||
guint16 *roots; /* Root record IDs (owned, g_free) */
|
||||
} ValidityDbInfo;
|
||||
|
||||
/* ================================================================
|
||||
* User Storage — returned by cmd 0x4B
|
||||
* Represents a named storage container that holds users.
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 user_count;
|
||||
gchar *name; /* owned, g_free */
|
||||
|
||||
/* Per-user entries: array of {dbid, value_size} pairs */
|
||||
guint16 *user_dbids;
|
||||
guint16 *user_val_sizes;
|
||||
} ValidityUserStorage;
|
||||
|
||||
/* ================================================================
|
||||
* Finger record entry — part of a User record
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 subtype; /* WINBIO finger constant (1-10) */
|
||||
guint16 storage;
|
||||
guint16 value_size;
|
||||
} ValidityFingerEntry;
|
||||
|
||||
/* ================================================================
|
||||
* User — returned by cmd 0x4A
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 finger_count;
|
||||
guint8 *identity; /* Raw identity bytes, owned */
|
||||
gsize identity_len;
|
||||
ValidityFingerEntry *fingers; /* owned array of finger_count entries */
|
||||
} ValidityUser;
|
||||
|
||||
/* ================================================================
|
||||
* DB Record — returned by cmd 0x49 (get_record_value)
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 type;
|
||||
guint16 storage;
|
||||
guint8 *value; /* owned, g_free */
|
||||
gsize value_len;
|
||||
} ValidityDbRecord;
|
||||
|
||||
/* ================================================================
|
||||
* Record child entry — from cmd 0x46 (get_record_children)
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 type;
|
||||
} ValidityRecordChild;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint16 dbid;
|
||||
guint16 type;
|
||||
guint16 storage;
|
||||
guint16 child_count;
|
||||
ValidityRecordChild *children; /* owned array */
|
||||
} ValidityRecordChildren;
|
||||
|
||||
/* ================================================================
|
||||
* Match result — parsed from cmd 0x60 (get_match_result) response
|
||||
* ================================================================ */
|
||||
typedef struct
|
||||
{
|
||||
gboolean matched; /* TRUE if user_dbid (tag 1) was found */
|
||||
guint32 user_dbid; /* tag 1: matched user record dbid */
|
||||
guint16 subtype; /* tag 3: matched finger subtype */
|
||||
guint8 *hash; /* tag 4: match hash (owned, g_free) */
|
||||
gsize hash_len;
|
||||
} ValidityMatchResult;
|
||||
|
||||
void validity_match_result_clear (ValidityMatchResult *result);
|
||||
|
||||
gboolean validity_parse_match_result (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityMatchResult *result);
|
||||
|
||||
/* ================================================================
|
||||
* Command builders — produce binary TLS command payloads
|
||||
*
|
||||
* All returned buffers are g_malloc'd and must be g_free'd by caller.
|
||||
* ================================================================ */
|
||||
|
||||
/* cmd 0x4B: Get user storage by name */
|
||||
guint8 *validity_db_build_cmd_get_user_storage (const gchar *name,
|
||||
gsize *out_len);
|
||||
|
||||
/* cmd 0x4A: Get user by dbid */
|
||||
guint8 *validity_db_build_cmd_get_user (guint16 dbid,
|
||||
gsize *out_len);
|
||||
|
||||
/* cmd 0x47: New record */
|
||||
guint8 *validity_db_build_cmd_new_record (guint16 parent,
|
||||
guint16 type,
|
||||
guint16 storage,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
gsize *out_len);
|
||||
|
||||
/* cmd 0x48: Delete record */
|
||||
guint8 *validity_db_build_cmd_del_record (guint16 dbid,
|
||||
gsize *out_len);
|
||||
|
||||
/* cmd 0x1a: Call cleanups (commit pending writes) */
|
||||
guint8 *validity_db_build_cmd_call_cleanups (gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Response parsers — parse binary TLS response payloads
|
||||
*
|
||||
* All parse functions take data AFTER the 2-byte status has been stripped.
|
||||
* Return TRUE on success, FALSE on parse error.
|
||||
* ================================================================ */
|
||||
|
||||
gboolean validity_db_parse_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityDbInfo *out);
|
||||
|
||||
gboolean validity_db_parse_user_storage (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityUserStorage *out);
|
||||
|
||||
gboolean validity_db_parse_user (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityUser *out);
|
||||
|
||||
gboolean validity_db_parse_record_value (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityDbRecord *out);
|
||||
|
||||
gboolean validity_db_parse_record_children (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityRecordChildren *out);
|
||||
|
||||
/* cmd 0x47 response: parse new record ID */
|
||||
gboolean validity_db_parse_new_record_id (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 *out_record_id);
|
||||
|
||||
/* ================================================================
|
||||
* Identity helpers
|
||||
* ================================================================ */
|
||||
|
||||
/* Build a UUID-based identity suitable for the sensor DB.
|
||||
* Returns a buffer of at least VALIDITY_IDENTITY_MIN_SIZE bytes. */
|
||||
guint8 *validity_db_build_identity (const gchar *uuid_str,
|
||||
gsize *out_len);
|
||||
|
||||
/* Build finger data payload for new_finger (format from make_finger_data) */
|
||||
guint8 *validity_db_build_finger_data (guint16 subtype,
|
||||
const guint8 *template_data,
|
||||
gsize template_len,
|
||||
const guint8 *tid,
|
||||
gsize tid_len,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Structure cleanup helpers
|
||||
* ================================================================ */
|
||||
|
||||
void validity_db_info_clear (ValidityDbInfo *info);
|
||||
void validity_user_storage_clear (ValidityUserStorage *storage);
|
||||
void validity_user_clear (ValidityUser *user);
|
||||
void validity_db_record_clear (ValidityDbRecord *record);
|
||||
void validity_record_children_clear (ValidityRecordChildren *children);
|
||||
|
||||
/* ================================================================
|
||||
* Enrollment commands
|
||||
* ================================================================ */
|
||||
|
||||
/* cmd 0x69: Create enrollment (start) / End enrollment */
|
||||
guint8 *validity_db_build_cmd_create_enrollment (gboolean start,
|
||||
gsize *out_len);
|
||||
|
||||
/* cmd 0x6B: Enrollment update (with template data) */
|
||||
guint8 *validity_db_build_cmd_enrollment_update (const guint8 *prev_data,
|
||||
gsize prev_len,
|
||||
gsize *out_len);
|
||||
|
||||
/* ================================================================
|
||||
* Match commands (inlined at call sites in verify/enroll SSMs)
|
||||
* ================================================================ */
|
||||
|
||||
/* ================================================================
|
||||
* db_write_enable blob access
|
||||
* ================================================================ */
|
||||
|
||||
typedef struct _FpiDeviceValidity FpiDeviceValidity;
|
||||
|
||||
const guint8 *validity_db_get_write_enable_blob (FpiDeviceValidity *self,
|
||||
gsize *out_len);
|
||||
1379
libfprint/drivers/validity/validity_enroll.c
Normal file
1379
libfprint/drivers/validity/validity_enroll.c
Normal file
File diff suppressed because it is too large
Load diff
697
libfprint/drivers/validity/validity_fwext.c
Normal file
697
libfprint/drivers/validity/validity_fwext.c
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
* Validity/Synaptics VCSFW firmware extension upload
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "drivers_api.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "validity.h"
|
||||
#include "validity_fwext.h"
|
||||
#include "validity_hal.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
#include "fpi-byte-writer.h"
|
||||
#include "validity_pack.h"
|
||||
#include <gio/gio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- Constants ---- */
|
||||
|
||||
#define FWEXT_CHUNK_SIZE 0x1000 /* 4 KB per write_flash chunk */
|
||||
#define FWEXT_SIGNATURE_SIZE 256 /* RSA signature length */
|
||||
#define FWEXT_HEADER_DELIMITER 0x1A /* .xpfwext header end marker */
|
||||
|
||||
#define FWEXT_HW_REG_WRITE_ADDR 0x8000205C
|
||||
#define FWEXT_HW_REG_WRITE_VALUE 7
|
||||
#define FWEXT_HW_REG_READ_ADDR 0x80002080
|
||||
|
||||
/* Firmware partition */
|
||||
#define FWEXT_PARTITION 2
|
||||
|
||||
/* Reboot command: 0x05 0x02 0x00 */
|
||||
#define VCSFW_CMD_REBOOT 0x05
|
||||
#define VCSFW_REBOOT_SUBCMD 0x02
|
||||
|
||||
/* Cleanup command (call_cleanups): 0x1a */
|
||||
#define VCSFW_CMD_CLEANUP 0x1A
|
||||
|
||||
/* ---- Firmware file search paths ---- */
|
||||
|
||||
static const gchar *firmware_search_paths[] = {
|
||||
"/usr/share/libfprint/validity",
|
||||
"/var/lib/python-validity",
|
||||
"/var/run/python-validity",
|
||||
"/usr/share/python-validity",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* db_write_enable blobs now live in HAL (validity_hal.c) */
|
||||
|
||||
/* ================================================================
|
||||
* Firmware info parsing
|
||||
* ================================================================ */
|
||||
|
||||
gboolean
|
||||
validity_fwext_parse_fw_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 status,
|
||||
ValidityFwInfo *info)
|
||||
{
|
||||
memset (info, 0, sizeof (*info));
|
||||
|
||||
/* Status 0xB004 (bytes: 0xb0 0x04) means no firmware loaded.
|
||||
* python-validity checks: rsp[0]==0xb0 and rsp[1]==4
|
||||
* Our vcsfw_cmd_send reads status as uint16 LE from the first 2 bytes,
|
||||
* so wire bytes {0xb0, 0x04} -> status = 0x04B0 in LE. */
|
||||
if (status == VCSFW_STATUS_NO_FW)
|
||||
{
|
||||
info->loaded = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("GET_FW_INFO unexpected status: 0x%04x", status);
|
||||
info->loaded = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Response data (after 2-byte status stripped by vcsfw_cmd_send):
|
||||
* major(2) + minor(2) + modcnt(2) + buildtime(4) = 10 bytes header
|
||||
* + 12 bytes per module */
|
||||
if (data_len < 10)
|
||||
{
|
||||
fp_warn ("GET_FW_INFO response too short: %zu", data_len);
|
||||
info->loaded = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
info->loaded = TRUE;
|
||||
|
||||
if (!validity_unpack (data, data_len, "hhhw",
|
||||
&info->major, &info->minor,
|
||||
&info->module_count, &info->buildtime))
|
||||
{
|
||||
info->loaded = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (info->module_count > 32)
|
||||
info->module_count = 32;
|
||||
|
||||
for (guint16 i = 0; i < info->module_count; i++)
|
||||
{
|
||||
if (!validity_unpack (data + 10 + i * 12, data_len - 10 - i * 12,
|
||||
"hhhhw",
|
||||
&info->modules[i].type,
|
||||
&info->modules[i].subtype,
|
||||
&info->modules[i].major,
|
||||
&info->modules[i].minor,
|
||||
&info->modules[i].size))
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Firmware filename mapping
|
||||
* ================================================================ */
|
||||
|
||||
const gchar *
|
||||
validity_fwext_get_firmware_name (guint16 vid,
|
||||
guint16 pid)
|
||||
{
|
||||
if (vid == 0x138a && pid == 0x0090)
|
||||
return "6_07f_Lenovo.xpfwext";
|
||||
|
||||
/* All other supported PIDs use the same firmware */
|
||||
if ((vid == 0x138a && (pid == 0x0097 || pid == 0x009d)) ||
|
||||
(vid == 0x06cb && pid == 0x009a))
|
||||
return "6_07f_lenovo_mis_qm.xpfwext";
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
validity_fwext_find_firmware (guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error)
|
||||
{
|
||||
const gchar *filename = validity_fwext_get_firmware_name (vid, pid);
|
||||
|
||||
if (filename == NULL)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"No firmware filename known for %04x:%04x", vid, pid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (const gchar **path = firmware_search_paths; *path != NULL; path++)
|
||||
{
|
||||
g_autofree gchar *full_path = g_build_filename (*path, filename, NULL);
|
||||
|
||||
if (g_file_test (full_path, G_FILE_TEST_IS_REGULAR))
|
||||
{
|
||||
fp_info ("Found firmware file: %s", full_path);
|
||||
return g_steal_pointer (&full_path);
|
||||
}
|
||||
}
|
||||
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_NOT_FOUND,
|
||||
"Firmware file '%s' not found. "
|
||||
"Search paths: /usr/share/libfprint/validity/, "
|
||||
"/var/lib/python-validity/, /var/run/python-validity/. "
|
||||
"Extract from Lenovo driver installer (nz3gf09w.exe).",
|
||||
filename);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* .xpfwext file parser
|
||||
* ================================================================ */
|
||||
|
||||
gboolean
|
||||
validity_fwext_load_file (const gchar *path,
|
||||
ValidityFwextFile *fwext,
|
||||
GError **error)
|
||||
{
|
||||
g_autofree guint8 *contents = NULL;
|
||||
gsize length = 0;
|
||||
|
||||
memset (fwext, 0, sizeof (*fwext));
|
||||
|
||||
if (!g_file_get_contents (path, (gchar **) &contents, &length, error))
|
||||
return FALSE;
|
||||
|
||||
/* Find 0x1A delimiter that ends the header */
|
||||
const guint8 *delim = memchr (contents, FWEXT_HEADER_DELIMITER, length);
|
||||
|
||||
if (delim == NULL)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Firmware file has no 0x1A header delimiter");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gsize header_len = (delim - contents) + 1; /* includes 0x1A itself */
|
||||
gsize data_len = length - header_len;
|
||||
|
||||
if (data_len < FWEXT_SIGNATURE_SIZE + 1)
|
||||
{
|
||||
g_set_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"Firmware file too short after header (%zu bytes)", data_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Split: payload = data[:-256], signature = data[-256:] */
|
||||
fwext->payload_len = data_len - FWEXT_SIGNATURE_SIZE;
|
||||
fwext->payload = g_memdup2 (contents + header_len, fwext->payload_len);
|
||||
memcpy (fwext->signature,
|
||||
contents + header_len + fwext->payload_len,
|
||||
FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
fp_info ("Firmware file loaded: %zu bytes payload, %d bytes signature",
|
||||
fwext->payload_len, FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_file_clear (ValidityFwextFile *fwext)
|
||||
{
|
||||
g_clear_pointer (&fwext->payload, g_free);
|
||||
fwext->payload_len = 0;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Command builders
|
||||
* ================================================================ */
|
||||
|
||||
void
|
||||
validity_fwext_build_write_hw_reg32 (guint32 addr,
|
||||
guint32 value,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
*cmd_len = validity_pack (cmd, 10, "bwwb",
|
||||
VCSFW_CMD_WRITE_HW_REG32, addr, value, 4);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_read_hw_reg32 (guint32 addr,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
*cmd_len = validity_pack (cmd, 6, "bwb",
|
||||
VCSFW_CMD_READ_HW_REG32, addr, 4);
|
||||
}
|
||||
|
||||
gboolean
|
||||
validity_fwext_parse_read_hw_reg32 (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint32 *value)
|
||||
{
|
||||
return validity_unpack (data, data_len, "w", value);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_write_flash (guint8 partition,
|
||||
guint32 offset,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
*cmd_len = validity_pack (cmd, 13 + data_len, "bbbhwwd",
|
||||
VCSFW_CMD_WRITE_FLASH, partition, 1,
|
||||
(guint16) 0, offset, (guint32) data_len,
|
||||
data, data_len);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_write_fw_sig (guint8 partition,
|
||||
const guint8 *signature,
|
||||
gsize sig_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
*cmd_len = validity_pack (cmd, 5 + sig_len, "bbxhd",
|
||||
VCSFW_CMD_WRITE_FW_SIG, partition,
|
||||
(guint16) sig_len, signature, sig_len);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_build_reboot (guint8 *cmd,
|
||||
gsize *cmd_len)
|
||||
{
|
||||
*cmd_len = validity_pack (cmd, 3, "bbb",
|
||||
VCSFW_CMD_REBOOT, VCSFW_REBOOT_SUBCMD, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* db_write_enable blob lookup
|
||||
* ================================================================ */
|
||||
|
||||
const guint8 *
|
||||
validity_fwext_get_db_write_enable (FpiDeviceValidity *self,
|
||||
gsize *len)
|
||||
{
|
||||
return validity_data_get_bytes (&self->device_data,
|
||||
VALIDITY_DATA_DB_WRITE_ENABLE,
|
||||
len);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Upload SSM -- firmware extension upload state machine
|
||||
*
|
||||
* This SSM is started as a standalone child from the open sequence
|
||||
* when fwext_loaded == FALSE. It uses the subsm pattern:
|
||||
* SEND states call vcsfw_tls_cmd_send(self, ssm, ..., NULL) with a
|
||||
* NULL callback. The child SSM auto-advances the parent to the
|
||||
* RECV state on completion. RECV states read
|
||||
* self->cmd_response_status and self->cmd_response_data.
|
||||
*
|
||||
* All commands are sent via TLS because the sensor requires
|
||||
* encrypted writes to flash partition 2.
|
||||
* ================================================================ */
|
||||
|
||||
/* SSM data for the upload state machine */
|
||||
typedef struct
|
||||
{
|
||||
ValidityFwextFile fwext;
|
||||
gsize write_offset; /* current offset into payload */
|
||||
guint16 vid;
|
||||
guint16 pid;
|
||||
} FwextUploadData;
|
||||
|
||||
static void
|
||||
fwext_upload_data_free (gpointer data)
|
||||
{
|
||||
FwextUploadData *ud = data;
|
||||
|
||||
validity_fwext_file_clear (&ud->fwext);
|
||||
g_free (ud);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
fwext_recv_read_hw_reg (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"READ_HW_REG failed: status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
|
||||
guint32 value;
|
||||
|
||||
if (!validity_fwext_parse_read_hw_reg32 (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&value))
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"READ_HW_REG response too short"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (value != 2 && value != 3)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Unexpected HW register value: %u "
|
||||
"(expected 2 or 3)", value));
|
||||
return;
|
||||
}
|
||||
|
||||
fp_dbg ("FWEXT: HW register 0x%08x = %u (OK)", FWEXT_HW_REG_READ_ADDR, value);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_load_file (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
FpDevice *dev = FP_DEVICE (self);
|
||||
FwextUploadData *ud = fpi_ssm_get_data (ssm);
|
||||
|
||||
GError *error = NULL;
|
||||
GUsbDevice *usb_dev = fpi_device_get_usb_device (dev);
|
||||
guint16 vid = g_usb_device_get_vid (usb_dev);
|
||||
guint16 pid = g_usb_device_get_pid (usb_dev);
|
||||
|
||||
ud->vid = vid;
|
||||
ud->pid = pid;
|
||||
|
||||
g_autofree gchar *fw_path = validity_fwext_find_firmware (vid, pid, &error);
|
||||
|
||||
if (fw_path == NULL)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validity_fwext_load_file (fw_path, &ud->fwext, &error))
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
ud->write_offset = 0;
|
||||
fp_info ("FWEXT: Loaded firmware file, %zu bytes to write",
|
||||
ud->fwext.payload_len);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_send_db_write_enable (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
gsize dbe_len;
|
||||
const guint8 *dbe = validity_fwext_get_db_write_enable (self, &dbe_len);
|
||||
|
||||
if (dbe == NULL || dbe_len == 0)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"No db_write_enable data for "
|
||||
"this device"));
|
||||
return;
|
||||
}
|
||||
|
||||
vcsfw_tls_cmd_send (self, ssm, dbe, dbe_len, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_send_write_chunk (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
FwextUploadData *ud = fpi_ssm_get_data (ssm);
|
||||
|
||||
gsize remaining = ud->fwext.payload_len - ud->write_offset;
|
||||
gsize chunk_size = MIN (remaining, FWEXT_CHUNK_SIZE);
|
||||
|
||||
/* cmd buffer: 13-byte header + payload */
|
||||
g_autofree guint8 *cmd = g_malloc (13 + chunk_size);
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_flash (FWEXT_PARTITION,
|
||||
(guint32) ud->write_offset,
|
||||
ud->fwext.payload + ud->write_offset,
|
||||
chunk_size,
|
||||
cmd, &cmd_len);
|
||||
|
||||
fp_dbg ("FWEXT: Writing chunk at offset 0x%zx (%zu/%zu bytes)",
|
||||
ud->write_offset, ud->write_offset + chunk_size,
|
||||
ud->fwext.payload_len);
|
||||
|
||||
ud->write_offset += chunk_size;
|
||||
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_recv_cleanup (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
FwextUploadData *ud = fpi_ssm_get_data (ssm);
|
||||
|
||||
/* Status 0x0491 means "nothing to commit" -- not an error */
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK &&
|
||||
self->cmd_response_status != 0x0491)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Cleanup cmd failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ud->write_offset < ud->fwext.payload_len)
|
||||
/* More chunks to write -- loop back to db_write_enable */
|
||||
fpi_ssm_jump_to_state (ssm, FWEXT_SEND_DB_WRITE_ENABLE);
|
||||
else
|
||||
/* All chunks written -- proceed to signature */
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_send_write_signature (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
FwextUploadData *ud = fpi_ssm_get_data (ssm);
|
||||
|
||||
guint8 cmd[5 + FWEXT_SIGNATURE_SIZE];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_fw_sig (FWEXT_PARTITION,
|
||||
ud->fwext.signature,
|
||||
FWEXT_SIGNATURE_SIZE,
|
||||
cmd, &cmd_len);
|
||||
|
||||
fp_info ("FWEXT: Writing firmware signature (%d bytes)",
|
||||
FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
fwext_recv_verify (FpiSsm *ssm,
|
||||
FpiDeviceValidity *self)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
if (!validity_fwext_parse_fw_info (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
self->cmd_response_status,
|
||||
&info) ||
|
||||
!info.loaded)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"Firmware not detected after upload"));
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("FWEXT: Upload verified -- firmware v%d.%d, %d modules",
|
||||
info.major, info.minor, info.module_count);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
|
||||
void
|
||||
validity_fwext_upload_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
switch (fpi_ssm_get_cur_state (ssm))
|
||||
{
|
||||
case FWEXT_SEND_WRITE_HW_REG:
|
||||
{
|
||||
guint8 cmd[10];
|
||||
gsize cmd_len;
|
||||
validity_fwext_build_write_hw_reg32 (FWEXT_HW_REG_WRITE_ADDR,
|
||||
FWEXT_HW_REG_WRITE_VALUE,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_HW_REG:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_HW_REG failed: status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
break;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_READ_HW_REG:
|
||||
{
|
||||
guint8 cmd[6];
|
||||
gsize cmd_len;
|
||||
validity_fwext_build_read_hw_reg32 (FWEXT_HW_REG_READ_ADDR,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_READ_HW_REG:
|
||||
fwext_recv_read_hw_reg (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_LOAD_FILE:
|
||||
fwext_load_file (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_DB_WRITE_ENABLE:
|
||||
fwext_send_db_write_enable (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_DB_WRITE_ENABLE:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"db_write_enable failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
break;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_WRITE_CHUNK:
|
||||
fwext_send_write_chunk (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_CHUNK:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_FLASH chunk failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
break;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_CLEANUP:
|
||||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_CLEANUP };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_CLEANUP:
|
||||
fwext_recv_cleanup (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_WRITE_SIGNATURE:
|
||||
fwext_send_write_signature (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_WRITE_SIGNATURE:
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
|
||||
"WRITE_FW_SIG failed: "
|
||||
"status=0x%04x",
|
||||
self->cmd_response_status));
|
||||
break;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_VERIFY:
|
||||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, FWEXT_PARTITION };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_VERIFY:
|
||||
fwext_recv_verify (ssm, self);
|
||||
break;
|
||||
|
||||
case FWEXT_SEND_REBOOT:
|
||||
{
|
||||
guint8 cmd[3];
|
||||
gsize cmd_len;
|
||||
validity_fwext_build_reboot (cmd, &cmd_len);
|
||||
fp_info ("FWEXT: Rebooting sensor to activate new firmware");
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FWEXT_RECV_REBOOT:
|
||||
fp_info ("FWEXT: Reboot sent. Device will re-enumerate.");
|
||||
fpi_ssm_mark_completed (ssm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- SSM factory ---- */
|
||||
|
||||
FpiSsm *
|
||||
validity_fwext_upload_ssm_new (FpDevice *dev)
|
||||
{
|
||||
FpiSsm *ssm;
|
||||
FwextUploadData *ud;
|
||||
|
||||
ssm = fpi_ssm_new (dev, validity_fwext_upload_run_state,
|
||||
FWEXT_NUM_STATES);
|
||||
ud = g_new0 (FwextUploadData, 1);
|
||||
fpi_ssm_set_data (ssm, ud, fwext_upload_data_free);
|
||||
|
||||
return ssm;
|
||||
}
|
||||
145
libfprint/drivers/validity/validity_fwext.h
Normal file
145
libfprint/drivers/validity/validity_fwext.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Validity/Synaptics VCSFW firmware extension upload
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/* ---- Firmware info from GET_FW_INFO response ---- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint16 type;
|
||||
guint16 subtype;
|
||||
guint16 major;
|
||||
guint16 minor;
|
||||
guint32 size;
|
||||
} ValidityFwModule;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gboolean loaded;
|
||||
guint16 major;
|
||||
guint16 minor;
|
||||
guint32 buildtime;
|
||||
guint16 module_count;
|
||||
ValidityFwModule modules[32];
|
||||
} ValidityFwInfo;
|
||||
|
||||
/* ---- Parsed firmware extension file ---- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
guint8 *payload; /* firmware data (without header, without signature) */
|
||||
gsize payload_len;
|
||||
guint8 signature[256]; /* 256-byte RSA signature */
|
||||
} ValidityFwextFile;
|
||||
|
||||
/* ---- Firmware upload SSM states ----
|
||||
*
|
||||
* Each command has a SEND and RECV pair:
|
||||
* - SEND: calls vcsfw_cmd_send(self, ssm, cmd, len, NULL)
|
||||
* which starts a child SSM (subsm). The parent is paused.
|
||||
* - RECV: entered automatically when child completes. Checks
|
||||
* self->cmd_response_status and self->cmd_response_data.
|
||||
* Advances or loops as needed.
|
||||
*/
|
||||
typedef enum {
|
||||
FWEXT_SEND_WRITE_HW_REG = 0,
|
||||
FWEXT_RECV_WRITE_HW_REG,
|
||||
FWEXT_SEND_READ_HW_REG,
|
||||
FWEXT_RECV_READ_HW_REG,
|
||||
FWEXT_LOAD_FILE,
|
||||
FWEXT_SEND_DB_WRITE_ENABLE,
|
||||
FWEXT_RECV_DB_WRITE_ENABLE,
|
||||
FWEXT_SEND_WRITE_CHUNK,
|
||||
FWEXT_RECV_WRITE_CHUNK,
|
||||
FWEXT_SEND_CLEANUP,
|
||||
FWEXT_RECV_CLEANUP,
|
||||
FWEXT_SEND_WRITE_SIGNATURE,
|
||||
FWEXT_RECV_WRITE_SIGNATURE,
|
||||
FWEXT_SEND_VERIFY,
|
||||
FWEXT_RECV_VERIFY,
|
||||
FWEXT_SEND_REBOOT,
|
||||
FWEXT_RECV_REBOOT,
|
||||
FWEXT_NUM_STATES,
|
||||
} ValidityFwextSsmState;
|
||||
|
||||
/* ---- API ---- */
|
||||
|
||||
gboolean validity_fwext_parse_fw_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 status,
|
||||
ValidityFwInfo *info);
|
||||
|
||||
gboolean validity_fwext_load_file (const gchar *filename,
|
||||
ValidityFwextFile *fwext,
|
||||
GError **error);
|
||||
|
||||
void validity_fwext_file_clear (ValidityFwextFile *fwext);
|
||||
|
||||
const gchar *validity_fwext_get_firmware_name (guint16 vid,
|
||||
guint16 pid);
|
||||
|
||||
gchar *validity_fwext_find_firmware (guint16 vid,
|
||||
guint16 pid,
|
||||
GError **error);
|
||||
|
||||
void validity_fwext_build_write_hw_reg32 (guint32 addr,
|
||||
guint32 value,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_read_hw_reg32 (guint32 addr,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
gboolean validity_fwext_parse_read_hw_reg32 (const guint8 *data,
|
||||
gsize data_len,
|
||||
guint32 *value);
|
||||
|
||||
void validity_fwext_build_write_flash (guint8 partition,
|
||||
guint32 offset,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_write_fw_sig (guint8 partition,
|
||||
const guint8 *signature,
|
||||
gsize sig_len,
|
||||
guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
void validity_fwext_build_reboot (guint8 *cmd,
|
||||
gsize *cmd_len);
|
||||
|
||||
typedef struct _FpiDeviceValidity FpiDeviceValidity;
|
||||
|
||||
const guint8 *validity_fwext_get_db_write_enable (FpiDeviceValidity *self,
|
||||
gsize *len);
|
||||
|
||||
/* SSM entry point for upload state machine */
|
||||
void validity_fwext_upload_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
/* Create the fwext upload SSM with data attached.
|
||||
* Caller starts it via fpi_ssm_start(). */
|
||||
FpiSsm *validity_fwext_upload_ssm_new (FpDevice *dev);
|
||||
122
libfprint/drivers/validity/validity_hal.c
Normal file
122
libfprint/drivers/validity/validity_hal.c
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Hardware Abstraction Layer for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Per-device blob data, flash partition layouts, and pairing constants.
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "validity_hal.h"
|
||||
#include "fpi-log.h"
|
||||
|
||||
/* ================================================================
|
||||
* Flash partition layouts
|
||||
*
|
||||
* Standard layout: used by PID 0097, 009a, 009d
|
||||
* Partition 4 (template DB) = 0x80000 bytes
|
||||
*
|
||||
* PID 0090 layout: smaller DB partition
|
||||
* Partition 4 (template DB) = 0x30000 bytes
|
||||
* ================================================================ */
|
||||
|
||||
/* Standard partition table (0097, 009a, 009d) */
|
||||
static const ValidityPartition flash_partitions_standard[] = {
|
||||
{ .id = 1, .type = 4, .access_lvl = 7, .offset = 0x00001000, .size = 0x00001000 }, /* cert store */
|
||||
{ .id = 2, .type = 1, .access_lvl = 2, .offset = 0x00002000, .size = 0x0003e000 }, /* xpfwext */
|
||||
{ .id = 5, .type = 5, .access_lvl = 3, .offset = 0x00040000, .size = 0x00008000 }, /* reserved */
|
||||
{ .id = 6, .type = 6, .access_lvl = 3, .offset = 0x00048000, .size = 0x00008000 }, /* calibration */
|
||||
{ .id = 4, .type = 3, .access_lvl = 5, .offset = 0x00050000, .size = 0x00080000 }, /* template DB */
|
||||
};
|
||||
|
||||
/* PID 0090 partition table (smaller DB) */
|
||||
static const ValidityPartition flash_partitions_0090[] = {
|
||||
{ .id = 1, .type = 4, .access_lvl = 7, .offset = 0x00001000, .size = 0x00001000 }, /* cert store */
|
||||
{ .id = 2, .type = 1, .access_lvl = 2, .offset = 0x00002000, .size = 0x0003e000 }, /* xpfwext */
|
||||
{ .id = 5, .type = 5, .access_lvl = 3, .offset = 0x00040000, .size = 0x00008000 }, /* reserved */
|
||||
{ .id = 6, .type = 6, .access_lvl = 3, .offset = 0x00048000, .size = 0x00008000 }, /* calibration */
|
||||
{ .id = 4, .type = 3, .access_lvl = 5, .offset = 0x00050000, .size = 0x00030000 }, /* template DB */
|
||||
};
|
||||
|
||||
/* Layout descriptors */
|
||||
static const ValidityFlashLayout flash_layout_standard = {
|
||||
.partitions = flash_partitions_standard,
|
||||
.num_partitions = G_N_ELEMENTS (flash_partitions_standard),
|
||||
};
|
||||
|
||||
static const ValidityFlashLayout flash_layout_0090 = {
|
||||
.partitions = flash_partitions_0090,
|
||||
.num_partitions = G_N_ELEMENTS (flash_partitions_0090),
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Per-device descriptors
|
||||
*
|
||||
* Blob data (init, reset, db_write_enable) has been moved to external
|
||||
* .bin files loaded at runtime by validity_data.c. Only VID/PID and
|
||||
* flash layout remain here.
|
||||
* ================================================================ */
|
||||
|
||||
static const ValidityDeviceDesc device_table[] = {
|
||||
[VALIDITY_HAL_DEV_90] = {
|
||||
.vid = 0x138a,
|
||||
.pid = 0x0090,
|
||||
.flash_layout = &flash_layout_0090,
|
||||
},
|
||||
|
||||
[VALIDITY_HAL_DEV_97] = {
|
||||
.vid = 0x138a,
|
||||
.pid = 0x0097,
|
||||
.flash_layout = &flash_layout_standard,
|
||||
},
|
||||
|
||||
[VALIDITY_HAL_DEV_9A] = {
|
||||
.vid = 0x06cb,
|
||||
.pid = 0x009a,
|
||||
.flash_layout = &flash_layout_standard,
|
||||
},
|
||||
|
||||
[VALIDITY_HAL_DEV_9D] = {
|
||||
.vid = 0x138a,
|
||||
.pid = 0x009d,
|
||||
.flash_layout = &flash_layout_standard,
|
||||
},
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* Lookup functions
|
||||
* ================================================================ */
|
||||
|
||||
const ValidityDeviceDesc *
|
||||
validity_hal_device_lookup (guint dev_type)
|
||||
{
|
||||
if (dev_type >= G_N_ELEMENTS (device_table))
|
||||
return NULL;
|
||||
|
||||
return &device_table[dev_type];
|
||||
}
|
||||
|
||||
const ValidityDeviceDesc *
|
||||
validity_hal_device_lookup_by_pid (guint16 vid, guint16 pid)
|
||||
{
|
||||
for (gsize i = 0; i < G_N_ELEMENTS (device_table); i++)
|
||||
if (device_table[i].vid == vid && device_table[i].pid == pid)
|
||||
return &device_table[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
81
libfprint/drivers/validity/validity_hal.h
Normal file
81
libfprint/drivers/validity/validity_hal.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Hardware Abstraction Layer for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Per-device blob data, flash partition layouts, and pairing constants.
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/* Forward declaration from validity.h */
|
||||
typedef enum {
|
||||
VALIDITY_HAL_DEV_90 = 0,
|
||||
VALIDITY_HAL_DEV_97,
|
||||
VALIDITY_HAL_DEV_9A,
|
||||
VALIDITY_HAL_DEV_9D,
|
||||
} ValidityHalDeviceType;
|
||||
|
||||
/* Flash partition entry — matches python-validity PartitionInfo:
|
||||
* id, type, access_lvl, offset, size */
|
||||
typedef struct
|
||||
{
|
||||
guint8 id;
|
||||
guint8 type;
|
||||
guint16 access_lvl;
|
||||
guint32 offset;
|
||||
guint32 size;
|
||||
} ValidityPartition;
|
||||
|
||||
/* Per-device flash layout with partition table.
|
||||
* The RSA partition signature is now loaded at runtime from external
|
||||
* data files (validity_data.c) instead of being compiled in. */
|
||||
typedef struct
|
||||
{
|
||||
const ValidityPartition *partitions;
|
||||
gsize num_partitions;
|
||||
} ValidityFlashLayout;
|
||||
|
||||
/* Per-device flash layout descriptor.
|
||||
* Blob data has been moved to external .bin files loaded at runtime
|
||||
* by validity_data.c. This struct retains only hardware identity
|
||||
* and flash partition layout. */
|
||||
typedef struct
|
||||
{
|
||||
guint16 vid;
|
||||
guint16 pid;
|
||||
|
||||
/* Flash partition layout for this device variant */
|
||||
const ValidityFlashLayout *flash_layout;
|
||||
} ValidityDeviceDesc;
|
||||
|
||||
/* Number of flash partition entries in the standard layout */
|
||||
#define VALIDITY_FLASH_NUM_PARTITIONS 5
|
||||
|
||||
/* Partition signature size (RSA-2048) */
|
||||
#define VALIDITY_PARTITION_SIG_SIZE 256
|
||||
|
||||
/* Look up device descriptor by ValidityDeviceType enum.
|
||||
* Returns NULL if dev_type is out of range. */
|
||||
const ValidityDeviceDesc *validity_hal_device_lookup (guint dev_type);
|
||||
|
||||
/* Look up device descriptor by USB VID/PID.
|
||||
* Returns NULL if no matching entry. */
|
||||
const ValidityDeviceDesc *validity_hal_device_lookup_by_pid (guint16 vid,
|
||||
guint16 pid);
|
||||
252
libfprint/drivers/validity/validity_pack.h
Normal file
252
libfprint/drivers/validity/validity_pack.h
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Validity VCSFW driver — pack/unpack utilities
|
||||
*
|
||||
* Varargs helpers for packing bytes into buffers and unpacking bytes from
|
||||
* buffers. Built on top of FpiByteWriter / FpiByteReader.
|
||||
*
|
||||
* Format codes (same for pack and unpack):
|
||||
* 'b' uint8
|
||||
* 'h' uint16 little-endian
|
||||
* 'H' uint16 big-endian
|
||||
* 'w' uint32 little-endian
|
||||
* 'W' uint32 big-endian
|
||||
* 't' uint24 big-endian (TLS lengths)
|
||||
* 'x' 1 pad / skip byte
|
||||
* 'd' data blob (pack: const guint8 *, gsize)
|
||||
* (unpack: const guint8 **, gsize — pointer set to internal)
|
||||
*
|
||||
* Pack into caller-provided buffer — returns bytes written:
|
||||
* gsize n = validity_pack (buf, sizeof buf, "bwwb", cmd, addr, val, len);
|
||||
*
|
||||
* Pack into newly allocated buffer — returns g_malloc'd pointer:
|
||||
* guint8 *p = validity_pack_new (&out_len, "bhd", cmd, id, data, data_len);
|
||||
*
|
||||
* Unpack from buffer — returns TRUE on success:
|
||||
* gboolean ok = validity_unpack (data, len, "hhxxh", &a, &b, &c);
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fpi-byte-writer.h" /* includes fpi-byte-reader.h */
|
||||
#include <stdarg.h>
|
||||
|
||||
/* ---- internal: write one format char ---- */
|
||||
static inline gboolean
|
||||
validity_pack_one (FpiByteWriter *w,
|
||||
char code,
|
||||
va_list *ap)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 'b':
|
||||
return fpi_byte_writer_put_uint8 (w, (guint8) va_arg (*ap, int));
|
||||
|
||||
case 'h':
|
||||
return fpi_byte_writer_put_uint16_le (w, (guint16) va_arg (*ap, int));
|
||||
|
||||
case 'H':
|
||||
return fpi_byte_writer_put_uint16_be (w, (guint16) va_arg (*ap, int));
|
||||
|
||||
case 'w':
|
||||
return fpi_byte_writer_put_uint32_le (w, va_arg (*ap, guint32));
|
||||
|
||||
case 'W':
|
||||
return fpi_byte_writer_put_uint32_be (w, va_arg (*ap, guint32));
|
||||
|
||||
case 't':
|
||||
return fpi_byte_writer_put_uint24_be (w, va_arg (*ap, guint32));
|
||||
|
||||
case 'x':
|
||||
return fpi_byte_writer_put_uint8 (w, 0);
|
||||
|
||||
case 'd':
|
||||
{
|
||||
const guint8 *d = va_arg (*ap, const guint8 *);
|
||||
gsize len = va_arg (*ap, gsize);
|
||||
if (len == 0)
|
||||
return TRUE;
|
||||
return fpi_byte_writer_put_data (w, d, len);
|
||||
}
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- internal: read one format char ---- */
|
||||
static inline gboolean
|
||||
validity_unpack_one (FpiByteReader *r,
|
||||
char code,
|
||||
va_list *ap)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 'b':
|
||||
return fpi_byte_reader_get_uint8 (r, va_arg (*ap, guint8 *));
|
||||
|
||||
case 'h':
|
||||
return fpi_byte_reader_get_uint16_le (r, va_arg (*ap, guint16 *));
|
||||
|
||||
case 'H':
|
||||
return fpi_byte_reader_get_uint16_be (r, va_arg (*ap, guint16 *));
|
||||
|
||||
case 'w':
|
||||
return fpi_byte_reader_get_uint32_le (r, va_arg (*ap, guint32 *));
|
||||
|
||||
case 'W':
|
||||
return fpi_byte_reader_get_uint32_be (r, va_arg (*ap, guint32 *));
|
||||
|
||||
case 't':
|
||||
return fpi_byte_reader_get_uint24_be (r, va_arg (*ap, guint32 *));
|
||||
|
||||
case 'x':
|
||||
return fpi_byte_reader_skip (r, 1);
|
||||
|
||||
case 'd':
|
||||
{
|
||||
const guint8 **out = va_arg (*ap, const guint8 * *);
|
||||
gsize len = va_arg (*ap, gsize);
|
||||
return fpi_byte_reader_get_data (r, len, out);
|
||||
}
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* validity_pack:
|
||||
* @buf: destination buffer (caller-provided)
|
||||
* @buf_size: size of @buf in bytes
|
||||
* @fmt: format string (see header comment)
|
||||
* @...: values matching each format code
|
||||
*
|
||||
* Packs fields into @buf according to @fmt.
|
||||
*
|
||||
* Returns: number of bytes written.
|
||||
*/
|
||||
G_GNUC_UNUSED static gsize
|
||||
validity_pack (guint8 *buf,
|
||||
gsize buf_size,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
FpiByteWriter w;
|
||||
va_list ap;
|
||||
|
||||
fpi_byte_writer_init_with_data (&w, buf, buf_size, FALSE);
|
||||
va_start (ap, fmt);
|
||||
for (const char *p = fmt; *p; p++)
|
||||
validity_pack_one (&w, *p, &ap);
|
||||
va_end (ap);
|
||||
return fpi_byte_writer_get_pos (&w);
|
||||
}
|
||||
|
||||
/**
|
||||
* validity_pack_new:
|
||||
* @out_len: (out): set to the number of bytes written
|
||||
* @fmt: format string (see header comment)
|
||||
* @...: values matching each format code
|
||||
*
|
||||
* Packs fields into a newly allocated buffer.
|
||||
*
|
||||
* Returns: (transfer full): a g_malloc'd buffer. Free with g_free().
|
||||
*/
|
||||
G_GNUC_UNUSED static guint8 *
|
||||
validity_pack_new (gsize *out_len,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
FpiByteWriter w;
|
||||
va_list ap;
|
||||
|
||||
/* Compute total size from format so we allocate exactly once. */
|
||||
gsize size = 0;
|
||||
|
||||
va_start (ap, fmt);
|
||||
for (const char *p = fmt; *p; p++)
|
||||
{
|
||||
switch (*p)
|
||||
{
|
||||
case 'b':
|
||||
size += 1;
|
||||
(void) va_arg (ap, int);
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
size += 1;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
case 'H':
|
||||
size += 2;
|
||||
(void) va_arg (ap, int);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
size += 3;
|
||||
(void) va_arg (ap, guint32);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
case 'W':
|
||||
size += 4;
|
||||
(void) va_arg (ap, guint32);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
(void) va_arg (ap, const guint8 *);
|
||||
size += va_arg (ap, gsize);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end (ap);
|
||||
|
||||
fpi_byte_writer_init_with_size (&w, size, FALSE);
|
||||
|
||||
va_start (ap, fmt);
|
||||
for (const char *p = fmt; *p; p++)
|
||||
validity_pack_one (&w, *p, &ap);
|
||||
va_end (ap);
|
||||
|
||||
*out_len = fpi_byte_writer_get_pos (&w);
|
||||
return fpi_byte_writer_reset_and_get_data (&w);
|
||||
}
|
||||
|
||||
/**
|
||||
* validity_unpack:
|
||||
* @data: source buffer
|
||||
* @data_len: length of @data
|
||||
* @fmt: format string (see header comment)
|
||||
* @...: pointers matching each format code
|
||||
*
|
||||
* Unpacks fields from @data according to @fmt. Stops on the first
|
||||
* bounds error.
|
||||
*
|
||||
* Returns: %TRUE if every field was read, %FALSE on short data.
|
||||
*/
|
||||
G_GNUC_UNUSED static gboolean
|
||||
validity_unpack (const guint8 *data,
|
||||
gsize data_len,
|
||||
const char *fmt,
|
||||
...)
|
||||
{
|
||||
FpiByteReader r;
|
||||
va_list ap;
|
||||
|
||||
fpi_byte_reader_init (&r, data, data_len);
|
||||
va_start (ap, fmt);
|
||||
for (const char *p = fmt; *p; p++)
|
||||
{
|
||||
if (!validity_unpack_one (&r, *p, &ap))
|
||||
{
|
||||
va_end (ap);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
va_end (ap);
|
||||
return TRUE;
|
||||
}
|
||||
1429
libfprint/drivers/validity/validity_pair.c
Normal file
1429
libfprint/drivers/validity/validity_pair.c
Normal file
File diff suppressed because it is too large
Load diff
331
libfprint/drivers/validity/validity_pair.h
Normal file
331
libfprint/drivers/validity/validity_pair.h
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Device pairing for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Handles pairing of uninitialized devices: flash partitioning,
|
||||
* ECDH key exchange, certificate creation, and TLS flash persistence.
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "validity_hal.h"
|
||||
|
||||
/* Forward declaration */
|
||||
typedef struct _FpiDeviceValidity FpiDeviceValidity;
|
||||
typedef struct _FpiSsm FpiSsm;
|
||||
|
||||
/* Flash IC parameters — returned by CMD 0x3e (GET_FLASH_INFO) */
|
||||
typedef struct
|
||||
{
|
||||
guint32 size;
|
||||
guint32 sector_size;
|
||||
guint8 sector_erase_cmd;
|
||||
} ValidityFlashIcParams;
|
||||
|
||||
/* Pairing state kept across SSM states */
|
||||
typedef struct
|
||||
{
|
||||
/* Flash IC params from CMD 0x3e */
|
||||
ValidityFlashIcParams flash_ic;
|
||||
guint16 num_partitions; /* 0 = needs pairing */
|
||||
|
||||
/* ECDH client key pair (generated during pairing) */
|
||||
EVP_PKEY *client_key;
|
||||
|
||||
/* Server certificate returned by partition_flash (CMD 0x4f) */
|
||||
guint8 *server_cert;
|
||||
gsize server_cert_len;
|
||||
|
||||
/* ECDH blob from CMD 0x50 (server's DH params) */
|
||||
guint8 *ecdh_blob;
|
||||
gsize ecdh_blob_len;
|
||||
|
||||
/* Encrypted private key blob (client → flash) */
|
||||
guint8 *priv_blob;
|
||||
gsize priv_blob_len;
|
||||
|
||||
/* Device descriptor for this PID */
|
||||
const ValidityDeviceDesc *dev_desc;
|
||||
|
||||
/* Flash erase progress counter */
|
||||
guint erase_step;
|
||||
|
||||
/* Set TRUE when reboot command has been sent (normal pairing or factory reset) */
|
||||
gboolean reboot_pending;
|
||||
} ValidityPairState;
|
||||
|
||||
/* Partition entry serialized format: 12 bytes data + 4 zero + 32 SHA-256 = 48 */
|
||||
#define VALIDITY_PARTITION_ENTRY_SIZE 48
|
||||
|
||||
/* Client certificate size */
|
||||
#define VALIDITY_CLIENT_CERT_SIZE 444
|
||||
|
||||
/* CMD 0x4f header IDs */
|
||||
#define VALIDITY_HDR_FLASH_IC 0
|
||||
#define VALIDITY_HDR_PARTITION_TABLE 1
|
||||
#define VALIDITY_HDR_CA_CERT 3
|
||||
#define VALIDITY_HDR_CLIENT_CERT 5
|
||||
|
||||
/* Encrypted private key format: 0x02 prefix + IV(16) + ciphertext(112) + HMAC(32) = 161 */
|
||||
#define VALIDITY_ENCRYPTED_KEY_PREFIX 0x02
|
||||
#define VALIDITY_ENCRYPTED_KEY_IV_SIZE 16
|
||||
#define VALIDITY_EC_COORD_SIZE 32
|
||||
|
||||
/* Flash partition IDs for erase during pairing */
|
||||
static const guint8 pair_erase_partition_ids[] = { 1, 2, 5, 6, 4 };
|
||||
#define VALIDITY_PAIR_NUM_ERASE_STEPS G_N_ELEMENTS (pair_erase_partition_ids)
|
||||
|
||||
/* ---- Helper functions (testable independently) ---- */
|
||||
|
||||
/**
|
||||
* validity_pair_serialize_partition:
|
||||
* @part: partition entry to serialize
|
||||
* @out: output buffer (must be at least VALIDITY_PARTITION_ENTRY_SIZE bytes)
|
||||
*
|
||||
* Serialize a partition entry to binary format:
|
||||
* [id:1][type:1][access_lvl:2LE][offset:4LE][size:4LE][zeros:4][SHA256:32]
|
||||
*/
|
||||
void validity_pair_serialize_partition (const ValidityPartition *part,
|
||||
guint8 *out);
|
||||
|
||||
/**
|
||||
* validity_pair_build_partition_flash_cmd:
|
||||
* @flash_ic: flash IC parameters from CMD 0x3e
|
||||
* @layout: flash layout for this device
|
||||
* @client_public_x: ECDH client public key X coordinate (32 bytes, little-endian)
|
||||
* @client_public_y: ECDH client public key Y coordinate (32 bytes, little-endian)
|
||||
* @out_len: output command length
|
||||
*
|
||||
* Build the CMD 0x4f (PARTITION_FLASH) payload:
|
||||
* [4f 0000 0000]
|
||||
* [hdr0: flash IC params]
|
||||
* [hdr1: partition table + RSA signature]
|
||||
* [hdr5: client certificate (444 bytes)]
|
||||
* [hdr3: CA certificate]
|
||||
*
|
||||
* Returns: newly allocated command buffer, free with g_free().
|
||||
*/
|
||||
guint8 *validity_pair_build_partition_flash_cmd (const ValidityFlashIcParams *flash_ic,
|
||||
const ValidityFlashLayout *layout,
|
||||
const guint8 *partition_sig,
|
||||
gsize partition_sig_len,
|
||||
const guint8 *client_public_x,
|
||||
const guint8 *client_public_y,
|
||||
const guint8 *password,
|
||||
gsize password_len,
|
||||
const guint8 *ca_cert,
|
||||
gsize ca_cert_len,
|
||||
gsize *out_len);
|
||||
|
||||
/**
|
||||
* validity_pair_make_cert:
|
||||
* @client_public_x: ECDH client public key X (32 bytes, little-endian)
|
||||
* @client_public_y: ECDH client public key Y (32 bytes, little-endian)
|
||||
* @out_len: set to VALIDITY_CLIENT_CERT_SIZE on success
|
||||
*
|
||||
* Build a 444-byte client certificate with ECDSA signature.
|
||||
* The HS key is derived from the hardcoded password.
|
||||
*
|
||||
* Returns: newly allocated cert buffer, or NULL on error. Free with g_free().
|
||||
*/
|
||||
guint8 *validity_pair_make_cert (const guint8 *client_public_x,
|
||||
const guint8 *client_public_y,
|
||||
const guint8 *password,
|
||||
gsize password_len,
|
||||
gsize *out_len);
|
||||
|
||||
/**
|
||||
* validity_pair_encrypt_key:
|
||||
* @client_private: ECDH client private key scalar (32 bytes, little-endian)
|
||||
* @client_public_x: ECDH client public key X (32 bytes, little-endian)
|
||||
* @client_public_y: ECDH client public key Y (32 bytes, little-endian)
|
||||
* @psk_encryption_key: PSK encryption key (32 bytes)
|
||||
* @psk_validation_key: PSK validation key (32 bytes)
|
||||
* @out_len: output blob length
|
||||
*
|
||||
* Encrypt the ECDH private key for flash storage:
|
||||
* plaintext = x(32) + y(32) + d(32) + PKCS7 padding
|
||||
* ciphertext = AES-256-CBC(psk_encryption_key, random_iv, plaintext)
|
||||
* blob = 0x02 + iv(16) + ciphertext + HMAC-SHA256(psk_validation_key, iv+ciphertext)
|
||||
*
|
||||
* Returns: newly allocated blob, or NULL on error. Free with g_free().
|
||||
*/
|
||||
guint8 *validity_pair_encrypt_key (const guint8 *client_private,
|
||||
const guint8 *client_public_x,
|
||||
const guint8 *client_public_y,
|
||||
const guint8 *psk_encryption_key,
|
||||
const guint8 *psk_validation_key,
|
||||
gsize *out_len);
|
||||
|
||||
/**
|
||||
* validity_pair_parse_flash_info:
|
||||
* @data: response payload from CMD 0x3e (after 2-byte status)
|
||||
* @data_len: length of payload
|
||||
* @ic_out: output flash IC params
|
||||
* @num_partitions_out: output number of existing partitions
|
||||
*
|
||||
* Parse GET_FLASH_INFO (CMD 0x3e) response.
|
||||
* If num_partitions_out is 0, device needs pairing.
|
||||
*
|
||||
* Returns: TRUE on success, FALSE on parse error.
|
||||
*/
|
||||
gboolean validity_pair_parse_flash_info (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityFlashIcParams *ic_out,
|
||||
guint16 *num_partitions_out);
|
||||
|
||||
/**
|
||||
* validity_pair_state_init:
|
||||
* @state: pairing state to initialize
|
||||
*
|
||||
* Zero-initialize the pairing state.
|
||||
*/
|
||||
void validity_pair_state_init (ValidityPairState *state);
|
||||
|
||||
/**
|
||||
* validity_pair_state_free:
|
||||
* @state: pairing state to free
|
||||
*
|
||||
* Free any allocated resources in the pairing state.
|
||||
*/
|
||||
void validity_pair_state_free (ValidityPairState *state);
|
||||
|
||||
/* ================================================================
|
||||
* Pairing SSM
|
||||
*
|
||||
* The pairing SSM runs when an uninitialized device is detected
|
||||
* (CMD 0x3e returns 0 partitions). It formats the flash, exchanges
|
||||
* ECDH keys, establishes a TLS session, persists the TLS keys to
|
||||
* flash, and reboots the device.
|
||||
*
|
||||
* Phase 1 (pre-TLS, raw USB):
|
||||
* - GET_FLASH_INFO → check if pairing needed
|
||||
* - SEND_RESET_BLOB → reset device state
|
||||
* - GENERATE_KEYS → ECDH key generation
|
||||
* - PARTITION_FLASH → format flash + write certs (CMD 0x4f)
|
||||
* - RECV_PARTITION → parse server cert from response
|
||||
* - CMD50_SEND → request ECDH server params
|
||||
* - CMD50_RECV → parse ECDH blob + encrypt private key
|
||||
* - CLEANUPS_SEND → call cleanups (0x1a) after CMD 0x50
|
||||
* - CLEANUPS_RECV → receive cleanups response
|
||||
*
|
||||
* Phase 2 (TLS):
|
||||
* - TLS_HANDSHAKE → establish TLS session
|
||||
* - ERASE_DBE_SEND → send db_write_enable before erase
|
||||
* - ERASE_DBE_RECV → receive dbe response
|
||||
* - ERASE_SEND → erase partition (CMD 0x3f)
|
||||
* - ERASE_RECV → receive erase response
|
||||
* - ERASE_CLEAN_SEND → call cleanups after erase
|
||||
* - ERASE_CLEAN_RECV → receive cleanups response
|
||||
* - ERASE_LOOP → loop over 5 partitions
|
||||
* - WRITE_DBE_SEND → send db_write_enable before write
|
||||
* - WRITE_DBE_RECV → receive dbe response
|
||||
* - WRITE_FLASH_SEND → write TLS flash (CMD 0x41)
|
||||
* - WRITE_FLASH_RECV → receive write response
|
||||
* - WRITE_CLEAN_SEND → call cleanups after write
|
||||
* - WRITE_CLEAN_RECV → receive cleanups response
|
||||
* - REBOOT_SEND → reboot device (0x05 0x02 0x00)
|
||||
* - REBOOT_RECV → receive reboot response
|
||||
* - DONE → pairing complete
|
||||
* ================================================================ */
|
||||
|
||||
typedef enum {
|
||||
PAIR_GET_FLASH_INFO = 0,
|
||||
PAIR_GET_FLASH_INFO_RECV,
|
||||
PAIR_CHECK_NEEDED,
|
||||
PAIR_VERIFY_TLS_SEND,
|
||||
PAIR_VERIFY_TLS_RECV,
|
||||
PAIR_SEND_RESET_BLOB,
|
||||
PAIR_SEND_RESET_BLOB_RECV,
|
||||
PAIR_GENERATE_KEYS,
|
||||
PAIR_PARTITION_FLASH_SEND,
|
||||
PAIR_PARTITION_FLASH_RECV,
|
||||
PAIR_FACTORY_RESET_SEND,
|
||||
PAIR_FACTORY_RESET_RECV,
|
||||
PAIR_CMD50_SEND,
|
||||
PAIR_CMD50_RECV,
|
||||
PAIR_CMD50_PROCESS,
|
||||
PAIR_CLEANUPS_SEND,
|
||||
PAIR_CLEANUPS_RECV,
|
||||
PAIR_TLS_HANDSHAKE,
|
||||
PAIR_ERASE_DBE_SEND,
|
||||
PAIR_ERASE_DBE_RECV,
|
||||
PAIR_ERASE_SEND,
|
||||
PAIR_ERASE_RECV,
|
||||
PAIR_ERASE_CLEAN_SEND,
|
||||
PAIR_ERASE_CLEAN_RECV,
|
||||
PAIR_ERASE_LOOP,
|
||||
PAIR_WRITE_DBE_SEND,
|
||||
PAIR_WRITE_DBE_RECV,
|
||||
PAIR_WRITE_FLASH_SEND,
|
||||
PAIR_WRITE_FLASH_RECV,
|
||||
PAIR_WRITE_CLEAN_SEND,
|
||||
PAIR_WRITE_CLEAN_RECV,
|
||||
PAIR_REBOOT_SEND,
|
||||
PAIR_REBOOT_RECV,
|
||||
PAIR_DONE,
|
||||
PAIR_NUM_STATES,
|
||||
} ValidityPairSsmState;
|
||||
|
||||
/* CMD 0x1a (cleanups/commit) */
|
||||
#define VCSFW_CMD_CLEANUPS 0x1a
|
||||
|
||||
/* CMD 0x50 (get ECDH server params after partition_flash) */
|
||||
#define VCSFW_CMD_GET_ECDH 0x50
|
||||
|
||||
/* Reboot: 0x05 0x02 0x00 */
|
||||
#define VCSFW_CMD_REBOOT 0x05
|
||||
|
||||
/**
|
||||
* validity_pair_ssm_new:
|
||||
* @dev: the FpDevice
|
||||
*
|
||||
* Create a new pairing SSM. The caller must set up
|
||||
* self->pair_state.dev_desc before starting the SSM.
|
||||
*
|
||||
* Returns: a new FpiSsm.
|
||||
*/
|
||||
FpiSsm *validity_pair_ssm_new (FpDevice *dev);
|
||||
|
||||
/**
|
||||
* validity_pair_run_state:
|
||||
* @ssm: the pairing SSM
|
||||
* @dev: the FpDevice
|
||||
*
|
||||
* State machine runner for the pairing SSM.
|
||||
*/
|
||||
void validity_pair_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
/**
|
||||
* validity_pair_build_tls_flash:
|
||||
* @state: pairing state (must have priv_blob, server_cert, ecdh_blob set)
|
||||
* @out_len: output data length (always 0x1000 = 4096)
|
||||
*
|
||||
* Build the TLS flash image for persistence. Format matches
|
||||
* python-validity make_tls_flash(): a sequence of flash blocks
|
||||
* zero-padded to 4096 bytes.
|
||||
*
|
||||
* Returns: newly allocated 4096-byte buffer. Free with g_free().
|
||||
*/
|
||||
guint8 *validity_pair_build_tls_flash (const ValidityPairState *state,
|
||||
const guint8 *ca_cert,
|
||||
gsize ca_cert_len,
|
||||
gsize *out_len);
|
||||
443
libfprint/drivers/validity/validity_sensor.c
Normal file
443
libfprint/drivers/validity/validity_sensor.c
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Sensor identification and HAL tables for Validity/Synaptics VCSFW
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "drivers_api.h"
|
||||
#include "validity_pack.h"
|
||||
#include "validity_sensor.h"
|
||||
|
||||
/* ================================================================
|
||||
* Calibration blobs (indexed by SensorTypeInfo)
|
||||
*
|
||||
* Extracted from python-validity generated_tables.py.
|
||||
* Each blob is used during calibration to build the key_line for
|
||||
* the timeslot table (see get_key_line in python-validity).
|
||||
* ================================================================ */
|
||||
|
||||
/* Blob for 57K0-family: 0x00b5, 0x0199, 0x0885, 0x1055, 0x1825, 0x1ff5, 0x00ed
|
||||
* 112 bytes, matching line_width=112 */
|
||||
static const guint8 calib_blob_57k0[] = {
|
||||
0x9b, 0x9a, 0x99, 0x97, 0x96, 0x95, 0x93, 0x92,
|
||||
0x91, 0x8f, 0x8e, 0x8d, 0x8b, 0x8a, 0x89, 0x87,
|
||||
0x86, 0x85, 0x83, 0x82, 0x81, 0x7f, 0x7e, 0x7d,
|
||||
0x7b, 0x7a, 0x79, 0x77, 0x76, 0x75, 0x73, 0x72,
|
||||
0x71, 0x6f, 0x6e, 0x6d, 0x6b, 0x6a, 0x69, 0x67,
|
||||
0x66, 0x65, 0x63, 0x62, 0x61, 0x5f, 0x5e, 0x5d,
|
||||
0x5b, 0x5a, 0x59, 0x57, 0x56, 0x55, 0x52, 0x51,
|
||||
0x50, 0x4e, 0x4d, 0x4c, 0x4a, 0x49, 0x48, 0x46,
|
||||
0x45, 0x44, 0x42, 0x41, 0x40, 0x3e, 0x3d, 0x3c,
|
||||
0x3a, 0x39, 0x38, 0x36, 0x35, 0x34, 0x32, 0x31,
|
||||
0x30, 0x2e, 0x2d, 0x2c, 0x2a, 0x29, 0x28, 0x26,
|
||||
0x25, 0x24, 0x22, 0x21, 0x20, 0x1e, 0x1d, 0x1c,
|
||||
0x1a, 0x19, 0x18, 0x16, 0x15, 0x14, 0x12, 0x11,
|
||||
0x10, 0x0e, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x06,
|
||||
};
|
||||
|
||||
/* Blob for 73A0/73A1 family: 0x00b3, 0x143b
|
||||
* 85 bytes for 0x00b3 (line_width=85), 84 bytes for 0x143b */
|
||||
static const guint8 calib_blob_73a[] = {
|
||||
0x89, 0x87, 0x86, 0x85, 0x83, 0x82, 0x81, 0x7f,
|
||||
0x7e, 0x7d, 0x7b, 0x7a, 0x79, 0x77, 0x76, 0x75,
|
||||
0x73, 0x72, 0x71, 0x6f, 0x6e, 0x6d, 0x6b, 0x6a,
|
||||
0x69, 0x67, 0x66, 0x65, 0x63, 0x62, 0x61, 0x5f,
|
||||
0x5e, 0x5d, 0x5b, 0x5a, 0x59, 0x57, 0x56, 0x55,
|
||||
0x52, 0x51, 0x50, 0x4e, 0x4d, 0x4c, 0x4a, 0x49,
|
||||
0x48, 0x46, 0x45, 0x44, 0x42, 0x41, 0x40, 0x3e,
|
||||
0x3d, 0x3c, 0x3a, 0x39, 0x38, 0x36, 0x35, 0x34,
|
||||
0x32, 0x31, 0x30, 0x2e, 0x2d, 0x2c, 0x2a, 0x29,
|
||||
0x28, 0x26, 0x25, 0x24, 0x22, 0x21, 0x20, 0x1e,
|
||||
0x1d, 0x1c, 0x1a, 0x19, 0x18,
|
||||
};
|
||||
|
||||
/* Blob for 55E/55D family: 0x00db
|
||||
* 144 bytes (line_width=144) */
|
||||
static const guint8 calib_blob_55e[] = {
|
||||
0x93, 0x92, 0x91, 0x8f, 0x8e, 0x8d, 0x8b, 0x8a,
|
||||
0x89, 0x87, 0x86, 0x85, 0x83, 0x82, 0x81, 0x7f,
|
||||
0x7e, 0x7d, 0x7b, 0x7a, 0x79, 0x77, 0x76, 0x75,
|
||||
0x73, 0x72, 0x71, 0x6f, 0x6e, 0x6d, 0x6b, 0x6a,
|
||||
0x69, 0x67, 0x66, 0x65, 0x63, 0x62, 0x61, 0x5f,
|
||||
0x5e, 0x5d, 0x5b, 0x5a, 0x59, 0x57, 0x56, 0x55,
|
||||
0x52, 0x51, 0x50, 0x4e, 0x4d, 0x4c, 0x4a, 0x49,
|
||||
0x48, 0x46, 0x45, 0x44, 0x42, 0x41, 0x40, 0x3e,
|
||||
0x3d, 0x3c, 0x3a, 0x39, 0x38, 0x36, 0x35, 0x34,
|
||||
0x32, 0x31, 0x30, 0x2e, 0x2d, 0x2c, 0x2a, 0x29,
|
||||
0x28, 0x26, 0x25, 0x24, 0x22, 0x21, 0x20, 0x1e,
|
||||
0x1d, 0x1c, 0x1a, 0x19, 0x18, 0x16, 0x15, 0x14,
|
||||
0x12, 0x11, 0x10, 0x0e, 0x0d, 0x0c, 0x0a, 0x09,
|
||||
0x08, 0x06,
|
||||
/* remaining bytes filled to line_width=144 */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
* SensorTypeInfo table
|
||||
*
|
||||
* From python-validity generated_tables.py SensorTypeInfo.table.
|
||||
* Only sensor types relevant to supported USB devices are included.
|
||||
* ================================================================ */
|
||||
|
||||
static const ValiditySensorTypeInfo sensor_type_info_table[] = {
|
||||
/* 57K0 family (06cb:009a) */
|
||||
{ 0x00b5, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x0199, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x0885, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x1055, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x1825, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x1ff5, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
{ 0x00ed, 0x78, 2, 112, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
|
||||
/* 73A family */
|
||||
{ 0x00b3, 0x60, 2, 84, 85, calib_blob_73a, 85 },
|
||||
{ 0x143b, 0x5c, 2, 84, 84, calib_blob_73a, 84 },
|
||||
|
||||
/* 55E family */
|
||||
{ 0x00db, 0x98, 1, 144, 144, calib_blob_55e, sizeof (calib_blob_55e) },
|
||||
|
||||
/* 57K2 */
|
||||
{ 0x00e4, 0x78, 2, 100, 112, calib_blob_57k0, sizeof (calib_blob_57k0) },
|
||||
|
||||
/* 75B0 */
|
||||
{ 0x08b1, 0x58, 2, 78, 78, NULL, 0 },
|
||||
|
||||
/* 55B */
|
||||
{ 0x00e1, 0x58, 2, 78, 78, NULL, 0 },
|
||||
|
||||
/* 77A */
|
||||
{ 0x00ea, 0x5c, 1, 84, 84, NULL, 0 },
|
||||
};
|
||||
|
||||
#define SENSOR_TYPE_INFO_TABLE_LEN G_N_ELEMENTS (sensor_type_info_table)
|
||||
|
||||
/* ================================================================
|
||||
* DeviceInfo table
|
||||
*
|
||||
* From python-validity hw_tables.py dev_info_table.
|
||||
* Includes entries for hardware majors seen on supported USB devices.
|
||||
* ================================================================ */
|
||||
|
||||
static const ValidityDeviceInfo device_info_table[] = {
|
||||
/* major=0x004a: SYN 57K0 series (06cb:009a, ThinkPad T480s etc.) */
|
||||
{ 0x004a, 0x00b5, 0x01, 0xff, "SYN 57K0" },
|
||||
{ 0x004a, 0x0885, 0x02, 0xff, "SYN 57K1" },
|
||||
{ 0x004a, 0x1055, 0x03, 0xff, "SYN 57K0 HEK" },
|
||||
{ 0x004a, 0x00b5, 0x05, 0xff, "SYN 57K0 Gold1" },
|
||||
{ 0x004a, 0x00b5, 0x06, 0xff, "SYN 57K0 Gold2" },
|
||||
{ 0x004a, 0x00b5, 0x07, 0xff, "SYN 57K0 Gold3" },
|
||||
{ 0x004a, 0x00b5, 0x08, 0xff, "SYN 57K0 Silver" },
|
||||
{ 0x004a, 0x00b5, 0x09, 0xff, "SYN 57K0 FM114-001" },
|
||||
{ 0x004a, 0x00b5, 0x0a, 0xff, "SYN 57K0 FM94-006" },
|
||||
{ 0x004a, 0x00b5, 0x0b, 0xff, "SYN 57K0 FM94-007" },
|
||||
{ 0x004a, 0x1825, 0x0c, 0xff, "SYN 57K0 FM154-001" },
|
||||
{ 0x004a, 0x1825, 0x0d, 0xff, "SYN 57K0 FM155-001" },
|
||||
{ 0x004a, 0x1825, 0x0e, 0xff, "SYN 57K0 FM154-002" },
|
||||
{ 0x004a, 0x1825, 0x0f, 0xff, "SYN 57K0 FM154-003" },
|
||||
{ 0x004a, 0x00b5, 0x10, 0xff, "SYN 57K0 FM94-009" },
|
||||
{ 0x004a, 0x00b5, 0x11, 0xff, "SYN 57K0 FM94-010" },
|
||||
{ 0x004a, 0x00b5, 0x12, 0xff, "SYN 57K0 FM94-011" },
|
||||
{ 0x004a, 0x00b5, 0x13, 0xff, "SYN 57K0 FM3297-02" },
|
||||
{ 0x004a, 0x00b5, 0x14, 0xff, "SYN 57K0 FM3297-03" },
|
||||
|
||||
/* major=0x0190: post-firmware-update major (06cb:009a etc.) */
|
||||
{ 0x0190, 0x2449, 0x01, 0xff, "86C FM-3290-002" },
|
||||
{ 0x0190, 0x2449, 0x02, 0xff, "86C FM-3324-001" },
|
||||
{ 0x0190, 0x057b, 0x03, 0xff, "88B0 FM-3316-001" },
|
||||
{ 0x0190, 0x1ff5, 0x04, 0xff, "57K0 FM-3328-001" },
|
||||
{ 0x0190, 0x00b5, 0x05, 0xff, "57K0 FM-3297-004" },
|
||||
{ 0x0190, 0x0c6d, 0x06, 0xff, "57K0 FM-3297-005" },
|
||||
{ 0x0190, 0x00b5, 0x07, 0xff, "57K0 FM-3297-006" },
|
||||
{ 0x0190, 0x00b5, 0x08, 0xff, "57K0 FM-3297-007" },
|
||||
{ 0x0190, 0x00b5, 0x09, 0xff, "57K0 FM-3297-008" },
|
||||
{ 0x0190, 0x00b5, 0x0a, 0xff, "57K0 FM-3297-009" },
|
||||
{ 0x0190, 0x00b5, 0x0b, 0xff, "57K0 FM-3297-010" },
|
||||
{ 0x0190, 0x00b5, 0x0c, 0xff, "57K0 FM-3297-011" },
|
||||
{ 0x0190, 0x057b, 0x0d, 0xff, "88B0 FM-3300-001" },
|
||||
{ 0x0190, 0x04c3, 0x0e, 0xff, "55E FM3327-FM3342" },
|
||||
{ 0x0190, 0x0191, 0x0f, 0xff, "57L0 FM-3331-001" },
|
||||
{ 0x0190, 0x0191, 0x10, 0xff, "57L0 FM-3331-002" },
|
||||
{ 0x0190, 0x0191, 0x11, 0xff, "57L0 FM-3331-003" },
|
||||
{ 0x0190, 0x0580, 0x12, 0xff, "88B0 FM-3310-001" },
|
||||
{ 0x0190, 0x0580, 0x13, 0xff, "88B0 FM-3310-002" },
|
||||
{ 0x0190, 0x0191, 0x14, 0xff, "57L0 FM-151-003" },
|
||||
{ 0x0190, 0x0191, 0x15, 0xff, "57L0 FM-211-002" },
|
||||
{ 0x0190, 0x0191, 0x16, 0xff, "57L0 FM-3299-002" },
|
||||
{ 0x0190, 0x0d49, 0x17, 0xff, "57L0 FM-3331-004" },
|
||||
{ 0x0190, 0x1131, 0x18, 0xff, "57L0 FM-3331-005" },
|
||||
{ 0x0190, 0x0197, 0x19, 0xff, "73A0 FM-3332-001" },
|
||||
{ 0x0190, 0x0195, 0x1a, 0xff, "86D TM3329-001-003" },
|
||||
{ 0x0190, 0x0195, 0x1b, 0xff, "86D TM3329-002-006" },
|
||||
{ 0x0190, 0x0196, 0x1c, 0xff, "57K0 FM-155-003" },
|
||||
{ 0x0190, 0x2449, 0x1d, 0xff, "86C TM-3315-001" },
|
||||
{ 0x0190, 0x2449, 0x1e, 0xff, "86C TM-3315-002" },
|
||||
{ 0x0190, 0x2449, 0x1f, 0xff, "86C TM-3322-001" },
|
||||
{ 0x0190, 0x2449, 0x20, 0xff, "86C FM-3326-001" },
|
||||
{ 0x0190, 0x2449, 0x21, 0xff, "86C FM-3208-002" },
|
||||
{ 0x0190, 0x2449, 0x22, 0xff, "86C FM-3340-001" },
|
||||
{ 0x0190, 0x0196, 0x23, 0xff, "57K0 FM-155-004" },
|
||||
{ 0x0190, 0x00b5, 0x24, 0xff, "57K0 FM-3297-012" },
|
||||
{ 0x0190, 0x00b5, 0x25, 0xff, "57K0 FM-3297-013" },
|
||||
{ 0x0190, 0x0197, 0x26, 0xff, "73A0 FM-3341-001" },
|
||||
{ 0x0190, 0x2449, 0x28, 0xff, "86C TM-3315-003" },
|
||||
{ 0x0190, 0x00b5, 0x29, 0xff, "57K0 FM-3297-020" },
|
||||
{ 0x0190, 0x00b5, 0x2a, 0xff, "57K0 FM-3297-021" },
|
||||
{ 0x0190, 0x00b5, 0x2b, 0xff, "57K0 FM-3297-022" },
|
||||
{ 0x0190, 0x00b5, 0x2c, 0xff, "57K0 FM-3297-023" },
|
||||
{ 0x0190, 0x00b5, 0x2d, 0xff, "57K0 FM-3297-024" },
|
||||
{ 0x0190, 0x00b5, 0x2e, 0xff, "57K0 FM-3297-025" },
|
||||
{ 0x0190, 0x00b5, 0x2f, 0xff, "57K0 FM-3297-026" },
|
||||
{ 0x0190, 0x00b5, 0x30, 0xff, "57K0 FM-3297-027" },
|
||||
{ 0x0190, 0x00b5, 0x31, 0xff, "57K0 FM-3297-028" },
|
||||
{ 0x0190, 0x00b5, 0x32, 0xff, "57K0 FM-3297-029" },
|
||||
{ 0x0190, 0x00b5, 0x33, 0xff, "57K0 FM-3297-030" },
|
||||
{ 0x0190, 0x00b5, 0x34, 0xff, "57K0 FM-3297-031" },
|
||||
{ 0x0190, 0x00b5, 0x35, 0xff, "57K0 FM-3297-014" },
|
||||
{ 0x0190, 0x00b5, 0x36, 0xff, "57K0 FM-3297-015" },
|
||||
{ 0x0190, 0x00b5, 0x37, 0xff, "57K0 FM-3297-032" },
|
||||
{ 0x0190, 0x00b5, 0x38, 0xff, "57K0 FM-3297-033" },
|
||||
{ 0x0190, 0x057b, 0x39, 0xff, "88B0 FM-3300-002" },
|
||||
{ 0x0190, 0x00de, 0x3a, 0xff, "109A FM-3302-001" },
|
||||
{ 0x0190, 0x057e, 0x3b, 0xff, "57K0 FM-154-020" },
|
||||
{ 0x0190, 0x0581, 0x3c, 0xff, "57K0 FM-154-021" },
|
||||
{ 0x0190, 0x2449, 0x3d, 0xff, "86C TM-3226-001" },
|
||||
{ 0x0190, 0x0195, 0x3e, 0xff, "86D TM3329-004-007" },
|
||||
{ 0x0190, 0x0196, 0x3f, 0xff, "57K0 FM-155-002" },
|
||||
{ 0x0190, 0x1825, 0x41, 0xff, "57K0 FM-154-001" },
|
||||
{ 0x0190, 0x0581, 0x42, 0xff, "57K0 FM-154-022" },
|
||||
{ 0x0190, 0x057b, 0x43, 0xff, "88B0 FM3358-3359" },
|
||||
{ 0x0190, 0x057b, 0x44, 0xff, "88B0 FM-3358-002" },
|
||||
{ 0x0190, 0x057b, 0x45, 0xff, "88B0 FM-3359-001" },
|
||||
{ 0x0190, 0x00b5, 0x46, 0xff, "57K0 FM-3297-100" },
|
||||
{ 0x0190, 0x057e, 0x47, 0xff, "57K0 FM-154-200" },
|
||||
{ 0x0190, 0x0198, 0x49, 0xff, "88B0 FM-3366-001" },
|
||||
{ 0x0190, 0x0199, 0x4a, 0xff, "57K0 FM-3367-001" },
|
||||
{ 0x0190, 0x2449, 0x4b, 0xff, "86C TM-3368-001" },
|
||||
{ 0x0190, 0x00db, 0x4c, 0xff, "55E FM-209-005" },
|
||||
{ 0x0190, 0x0969, 0x4f, 0xff, "57K0 FM-154-023" },
|
||||
{ 0x0190, 0x0580, 0x50, 0xff, "88B0 FM-3373-001" },
|
||||
{ 0x0190, 0x00db, 0x51, 0xff, "55E FM-209-006" },
|
||||
{ 0x0190, 0x0581, 0x52, 0xff, "57K0 FM-154-001" },
|
||||
{ 0x0190, 0x0581, 0x53, 0xff, "57K0 FM-154-002" },
|
||||
{ 0x0190, 0x0581, 0x54, 0xff, "57K0 FM-154-003" },
|
||||
{ 0x0190, 0x0d51, 0x55, 0xff, "57K0 FM-154-020" },
|
||||
{ 0x0190, 0x0581, 0x56, 0xff, "57K0 FM-155-001" },
|
||||
{ 0x0190, 0x0199, 0x57, 0xff, "57K0 FM-155-002" },
|
||||
{ 0x0190, 0x0195, 0x58, 0xff, "86D TM-3329-005" },
|
||||
{ 0x0190, 0x0199, 0x59, 0xff, "57K0 FM-3367-002" },
|
||||
{ 0x0190, 0x0199, 0x5a, 0xff, "57K0 FM-3367-003" },
|
||||
{ 0x0190, 0x0199, 0x5b, 0xff, "57K0 FM-3367-004" },
|
||||
{ 0x0190, 0x00db, 0x5c, 0xff, "55E FM-160-004" },
|
||||
{ 0x0190, 0x0968, 0x5d, 0xff, "88B0 FM-3366-002" },
|
||||
{ 0x0190, 0x2449, 0x5e, 0xff, "86C TM-P3376-P3404" },
|
||||
{ 0x0190, 0x00b5, 0x5f, 0xff, "57K0 FM-3380-001" },
|
||||
{ 0x0190, 0x0199, 0x60, 0xff, "57K0 FM-3380-002" },
|
||||
{ 0x0190, 0x0199, 0x61, 0xff, "57K0 FM-3380-003" },
|
||||
{ 0x0190, 0x0199, 0x62, 0xff, "57K0 FM-3380-004" },
|
||||
{ 0x0190, 0x2449, 0x63, 0xff, "86C FM-3290-003" },
|
||||
{ 0x0190, 0x057b, 0x64, 0xff, "88B0 FM-3358-003" },
|
||||
{ 0x0190, 0x2449, 0x65, 0xff, "86C FM-3389-001" },
|
||||
{ 0x0190, 0x0199, 0x68, 0xff, "57K0 FM-3367-005" },
|
||||
{ 0x0190, 0x0199, 0x69, 0xff, "57K0 FM-3367-006" },
|
||||
{ 0x0190, 0x0199, 0x6a, 0xff, "57K0 FM-3380-001b" },
|
||||
{ 0x0190, 0x0191, 0x6b, 0xff, "57L0 FM-3396-001" },
|
||||
{ 0x0190, 0x0191, 0x6c, 0xff, "57L0 FM-3397-001" },
|
||||
{ 0x0190, 0x2449, 0x6e, 0xff, "86C TM3261-003-004" },
|
||||
{ 0x0190, 0x0581, 0x6f, 0xff, "57K0 FM-3395-001" },
|
||||
{ 0x0190, 0x0d51, 0x70, 0xff, "57K0 FM-154-120" },
|
||||
{ 0x0190, 0x0969, 0x71, 0xff, "57K0 FM-154-123" },
|
||||
{ 0x0190, 0x00b5, 0x72, 0xff, "57K0 FM-3401-001" },
|
||||
{ 0x0190, 0x00b5, 0x73, 0xff, "57K0 FM-3401-004" },
|
||||
{ 0x0190, 0x00b5, 0x74, 0xff, "57K0 FM-3401-005" },
|
||||
{ 0x0190, 0x00b5, 0x75, 0xff, "57K0 FM-3401-006" },
|
||||
{ 0x0190, 0x0199, 0x76, 0xff, "57K0 FM-155-005" },
|
||||
{ 0x0190, 0x0c6d, 0x79, 0xff, "57K0 FM-3297-034" },
|
||||
{ 0x0190, 0x00b5, 0x7a, 0xff, "57K0 FM-3297-035" },
|
||||
{ 0x0190, 0x057b, 0x7b, 0xff, "88B0 FM-3358-004" },
|
||||
{ 0x0190, 0x057b, 0x7c, 0xff, "88B0 FM-3358-005" },
|
||||
{ 0x0190, 0x0199, 0x7e, 0xff, "57K0 FM-155-007" },
|
||||
{ 0x0190, 0x0199, 0x82, 0xff, "57K0 FM-155-102" },
|
||||
{ 0x0190, 0x0d51, 0x83, 0xff, "57K0 FM-3439-001" },
|
||||
{ 0x0190, 0x2449, 0x84, 0xff, "86C FM-3324-002" },
|
||||
{ 0x0190, 0x0969, 0x85, 0xff, "57K0 FM-3439-002" },
|
||||
{ 0x0190, 0x2449, 0x86, 0xff, "86C FM-3324-003" },
|
||||
{ 0x0190, 0x0969, 0x87, 0xff, "57K0 FM-3439-003" },
|
||||
{ 0x0190, 0x0969, 0x88, 0xff, "57K0 FM-3439-004" },
|
||||
{ 0x0190, 0x0199, 0x89, 0xff, "57K0 FM-155-008" },
|
||||
{ 0x0190, 0x0581, 0x8a, 0xff, "57K0 FM-154-124" },
|
||||
{ 0x0190, 0x057b, 0x8b, 0xff, "88B0 FM-3358-007" },
|
||||
{ 0x0190, 0x0581, 0x8c, 0xff, "57K0 FM-3439-005" },
|
||||
{ 0x0190, 0x0969, 0x8d, 0xff, "57K0 FM-3439-006" },
|
||||
{ 0x0190, 0x0199, 0x8e, 0xff, "57K0 FM-155-103" },
|
||||
{ 0x0190, 0x0581, 0x8f, 0xff, "57K0 FM-154-125" },
|
||||
{ 0x0190, 0x0581, 0x90, 0xff, "57K0 FM-3439-007" },
|
||||
{ 0x0190, 0x0969, 0x91, 0xff, "57K0 FM-3439-008" },
|
||||
{ 0x0190, 0x0969, 0x92, 0xff, "57K0 FM-3439-009" },
|
||||
{ 0x0190, 0x0969, 0x93, 0xff, "57K0 FM-3439-010" },
|
||||
{ 0x0190, 0x0969, 0x94, 0xff, "57K0 FM-3439-011" },
|
||||
{ 0x0190, 0x0969, 0x95, 0xff, "57K0 FM-3439-108" },
|
||||
{ 0x0190, 0x0969, 0x96, 0xff, "57K0 FM-3439-109" },
|
||||
{ 0x0190, 0x0969, 0x97, 0xff, "57K0 FM-3439-110" },
|
||||
{ 0x0190, 0x057b, 0x98, 0xff, "88B0 FM-3358-008" },
|
||||
{ 0x0190, 0x057b, 0x99, 0xff, "88B0 FM-3358-009" },
|
||||
{ 0x0190, 0x0d51, 0x9a, 0xff, "57K0 FM-3439-101" },
|
||||
{ 0x0190, 0x0969, 0x9b, 0xff, "57K0 FM-3439-102" },
|
||||
{ 0x0190, 0x0969, 0x9c, 0xff, "57K0 FM-3439-012" },
|
||||
{ 0x0190, 0x1139, 0x9d, 0xff, "57K0 FM-3439-013" },
|
||||
{ 0x0190, 0x0969, 0x9e, 0xff, "57K0 FM-3439-014" },
|
||||
{ 0x0190, 0x0c6d, 0x9f, 0xff, "57K0 FM-3297-036" },
|
||||
{ 0x0190, 0x057b, 0xa0, 0xff, "88B0 FM-3358-010" },
|
||||
{ 0x0190, 0x057b, 0xa1, 0xff, "88B0 FM-3316-002" },
|
||||
{ 0x0190, 0x2449, 0xa2, 0xff, "86C TM-P3568-001" },
|
||||
{ 0x0190, 0x2449, 0xa3, 0xff, "86C TM-P3569-001" },
|
||||
|
||||
/* major=0x0071: VSI 55E (type 0xdb) */
|
||||
{ 0x0071, 0x00db, 0x01, 0xff, "VSI 55E FM72-001" },
|
||||
{ 0x0071, 0x00db, 0x02, 0xff, "VSI 55E FM72-002" },
|
||||
|
||||
/* major=0x007f: SYN 73A01 (type 0xb3) */
|
||||
{ 0x007f, 0x00b3, 0x01, 0xff, "SYN 73A1" },
|
||||
{ 0x007f, 0x00b3, 0x02, 0xff, "SYN 73A01 FM152-001" },
|
||||
{ 0x007f, 0x00b3, 0x04, 0xff, "SYN 73A01 FM153-001" },
|
||||
|
||||
/* major=0x0000: wildcard entries (match any version) */
|
||||
{ 0x0000, 0x00b5, 0x00, 0x00, "SYN 57F" },
|
||||
{ 0x0000, 0x00db, 0x00, 0x00, "VSI 55E" },
|
||||
};
|
||||
|
||||
#define DEVICE_INFO_TABLE_LEN G_N_ELEMENTS (device_info_table)
|
||||
|
||||
/* ================================================================
|
||||
* Identify sensor parser
|
||||
*
|
||||
* Cmd 0x75 response (after 2-byte status stripped):
|
||||
* [zeroes:4 LE] [version:2 LE] [major:2 LE]
|
||||
*
|
||||
* See python-validity sensor.py identify_sensor():
|
||||
* _, minor, major = unpack('<LHH', rsp[:8])
|
||||
* ================================================================ */
|
||||
|
||||
gboolean
|
||||
validity_sensor_parse_identify (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValiditySensorIdent *out)
|
||||
{
|
||||
guint32 zeroes;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
return validity_unpack (data, data_len, "whh",
|
||||
&zeroes, &out->hw_version, &out->hw_major);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* DeviceInfo lookup
|
||||
*
|
||||
* Matches python-validity hw_tables.py dev_info_lookup():
|
||||
* - Exact match: major matches AND (version & version_mask) == version
|
||||
* - Fuzzy match: major matches AND version_mask == 0 (wildcard)
|
||||
* - Exact match preferred over fuzzy
|
||||
* ================================================================ */
|
||||
|
||||
const ValidityDeviceInfo *
|
||||
validity_device_info_lookup (guint16 major,
|
||||
guint16 version)
|
||||
{
|
||||
const ValidityDeviceInfo *fuzzy_match = NULL;
|
||||
|
||||
for (gsize i = 0; i < DEVICE_INFO_TABLE_LEN; i++)
|
||||
{
|
||||
const ValidityDeviceInfo *entry = &device_info_table[i];
|
||||
|
||||
if (entry->major != major)
|
||||
continue;
|
||||
|
||||
guint8 masked_ver = entry->version & entry->version_mask;
|
||||
|
||||
if (version == 0 || masked_ver == 0)
|
||||
fuzzy_match = entry;
|
||||
else if ((guint8) version == masked_ver)
|
||||
return entry;
|
||||
}
|
||||
|
||||
return fuzzy_match;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* SensorTypeInfo lookup
|
||||
* ================================================================ */
|
||||
|
||||
const ValiditySensorTypeInfo *
|
||||
validity_sensor_type_info_lookup (guint16 sensor_type)
|
||||
{
|
||||
for (gsize i = 0; i < SENSOR_TYPE_INFO_TABLE_LEN; i++)
|
||||
if (sensor_type_info_table[i].sensor_type == sensor_type)
|
||||
return &sensor_type_info_table[i];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Factory bits command builder
|
||||
*
|
||||
* Cmd 0x6f: GET_FACTORY_BITS
|
||||
* Wire format: [0x6f] [tag:2 LE] [pad:2 LE = 0] [pad:4 LE = 0]
|
||||
* Total: 9 bytes
|
||||
*
|
||||
* See python-validity sensor.py:
|
||||
* tls.cmd(unhex('6f') + pack('<HHL', tag, 0, 0))
|
||||
* ================================================================ */
|
||||
|
||||
#define FACTORY_BITS_CMD_LEN 9
|
||||
|
||||
gsize
|
||||
validity_sensor_build_factory_bits_cmd (guint16 tag,
|
||||
guint8 *buf,
|
||||
gsize buf_len)
|
||||
{
|
||||
if (buf_len < FACTORY_BITS_CMD_LEN)
|
||||
return 0;
|
||||
|
||||
return validity_pack (buf, buf_len, "bhhw",
|
||||
0x6f, /* VCSFW_CMD_GET_FACTORY_BITS */
|
||||
tag, (guint16) 0, (guint32) 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Sensor state lifecycle
|
||||
* ================================================================ */
|
||||
|
||||
void
|
||||
validity_sensor_state_init (ValiditySensorState *state)
|
||||
{
|
||||
memset (state, 0, sizeof (*state));
|
||||
}
|
||||
|
||||
void
|
||||
validity_sensor_state_clear (ValiditySensorState *state)
|
||||
{
|
||||
g_clear_pointer (&state->factory_bits, g_free);
|
||||
memset (state, 0, sizeof (*state));
|
||||
}
|
||||
126
libfprint/drivers/validity/validity_sensor.h
Normal file
126
libfprint/drivers/validity/validity_sensor.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Sensor identification and HAL table types for Validity/Synaptics VCSFW
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* SensorTypeInfo — sensor geometry and calibration parameters.
|
||||
* Derived from python-validity SensorTypeInfo (generated_tables.py).
|
||||
*
|
||||
* Each sensor type has fixed imaging geometry (bytes per scan line,
|
||||
* repeat multiplier for frame size, calibration dimensions) and an
|
||||
* optional calibration lookup blob.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
guint16 sensor_type;
|
||||
guint16 bytes_per_line;
|
||||
guint8 repeat_multiplier;
|
||||
guint16 lines_per_calibration_data;
|
||||
guint16 line_width;
|
||||
const guint8 *calibration_blob; /* may be NULL */
|
||||
gsize calibration_blob_len;
|
||||
} ValiditySensorTypeInfo;
|
||||
|
||||
/*
|
||||
* DeviceInfo — hardware identity to sensor type mapping.
|
||||
* Derived from python-validity DeviceInfo (hw_tables.py).
|
||||
*
|
||||
* The identify_sensor command (0x75) returns a hardware major + version.
|
||||
* DeviceInfo maps those to a sensor type (→ SensorTypeInfo) and a human
|
||||
* readable name.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
guint16 major;
|
||||
guint16 type; /* sensor type for SensorTypeInfo lookup */
|
||||
guint8 version;
|
||||
guint8 version_mask; /* 0xff = exact match, 0x00 = wildcard */
|
||||
const char *name;
|
||||
} ValidityDeviceInfo;
|
||||
|
||||
/*
|
||||
* Sensor identification from cmd 0x75 response.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
guint16 hw_major; /* hardware major (→ DeviceInfo.major) */
|
||||
guint16 hw_version; /* hardware version (→ DeviceInfo.version) */
|
||||
} ValiditySensorIdent;
|
||||
|
||||
/*
|
||||
* Aggregate sensor state stored in FpiDeviceValidity.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
const ValidityDeviceInfo *device_info;
|
||||
const ValiditySensorTypeInfo *type_info;
|
||||
|
||||
/* Factory calibration bits (raw response from cmd 0x6f) */
|
||||
guint8 *factory_bits;
|
||||
gsize factory_bits_len;
|
||||
} ValiditySensorState;
|
||||
|
||||
/* ---- Parsing functions ---- */
|
||||
|
||||
/*
|
||||
* Parse the response from VCSFW_CMD_IDENTIFY_SENSOR (0x75).
|
||||
* Response format (after 2-byte status): zeroes(4LE) | version(2LE) | major(2LE).
|
||||
* Returns FALSE if the data is too short.
|
||||
*/
|
||||
gboolean validity_sensor_parse_identify (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValiditySensorIdent *out);
|
||||
|
||||
/* ---- HAL table lookups ---- */
|
||||
|
||||
/*
|
||||
* Look up a DeviceInfo entry by hardware major and version.
|
||||
* Exact match on (major, version & version_mask) is preferred;
|
||||
* falls back to fuzzy match when version_mask == 0.
|
||||
* Returns NULL if no match found.
|
||||
*/
|
||||
const ValidityDeviceInfo *validity_device_info_lookup (guint16 major,
|
||||
guint16 version);
|
||||
|
||||
/*
|
||||
* Look up a SensorTypeInfo entry by sensor type.
|
||||
* Returns NULL if the type is not in the table.
|
||||
*/
|
||||
const ValiditySensorTypeInfo *validity_sensor_type_info_lookup (guint16 sensor_type);
|
||||
|
||||
/* ---- Command building ---- */
|
||||
|
||||
/*
|
||||
* Build the command bytes for VCSFW_CMD_GET_FACTORY_BITS (0x6f).
|
||||
* Format: cmd(1) | tag(2LE) | pad(2LE=0) | pad(4LE=0) = 9 bytes.
|
||||
* Returns the number of bytes written, or 0 if buf_len < 9.
|
||||
*/
|
||||
gsize validity_sensor_build_factory_bits_cmd (guint16 tag,
|
||||
guint8 *buf,
|
||||
gsize buf_len);
|
||||
|
||||
/* ---- Lifecycle ---- */
|
||||
|
||||
void validity_sensor_state_init (ValiditySensorState *state);
|
||||
void validity_sensor_state_clear (ValiditySensorState *state);
|
||||
1962
libfprint/drivers/validity/validity_tls.c
Normal file
1962
libfprint/drivers/validity/validity_tls.c
Normal file
File diff suppressed because it is too large
Load diff
227
libfprint/drivers/validity/validity_tls.h
Normal file
227
libfprint/drivers/validity/validity_tls.h
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* TLS session management for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/ec.h>
|
||||
|
||||
/* TLS record content types */
|
||||
#define TLS_CONTENT_CHANGE_CIPHER 0x14
|
||||
#define TLS_CONTENT_HANDSHAKE 0x16
|
||||
#define TLS_CONTENT_APP_DATA 0x17
|
||||
|
||||
/* TLS version 1.2 */
|
||||
#define TLS_VERSION_MAJOR 0x03
|
||||
#define TLS_VERSION_MINOR 0x03
|
||||
|
||||
/* TLS handshake message types */
|
||||
#define TLS_HS_CLIENT_HELLO 0x01
|
||||
#define TLS_HS_SERVER_HELLO 0x02
|
||||
#define TLS_HS_CERTIFICATE 0x0B
|
||||
#define TLS_HS_CERT_REQUEST 0x0D
|
||||
#define TLS_HS_SERVER_HELLO_DONE 0x0E
|
||||
#define TLS_HS_CERT_VERIFY 0x0F
|
||||
#define TLS_HS_CLIENT_KEY_EXCHANGE 0x10
|
||||
#define TLS_HS_FINISHED 0x14
|
||||
|
||||
/* Cipher suite */
|
||||
#define TLS_CS_ECDH_ECDSA_AES256_CBC_SHA 0xC005
|
||||
|
||||
/* Key/block sizes */
|
||||
#define TLS_AES_KEY_SIZE 32
|
||||
#define TLS_IV_SIZE 16
|
||||
#define TLS_HMAC_SIZE 32
|
||||
#define TLS_AES_BLOCK_SIZE 16
|
||||
#define TLS_MASTER_SECRET_SIZE 48
|
||||
#define TLS_KEY_BLOCK_SIZE 0x120
|
||||
#define TLS_RANDOM_SIZE 32
|
||||
#define TLS_VERIFY_DATA_SIZE 12
|
||||
|
||||
/* VCSFW TLS command prefix */
|
||||
#define TLS_CMD_PREFIX_SIZE 4
|
||||
|
||||
/* Flash block IDs */
|
||||
#define TLS_FLASH_BLOCK_EMPTY0 0x0000
|
||||
#define TLS_FLASH_BLOCK_EMPTY1 0x0001
|
||||
#define TLS_FLASH_BLOCK_EMPTY2 0x0002
|
||||
#define TLS_FLASH_BLOCK_CERT 0x0003
|
||||
#define TLS_FLASH_BLOCK_PRIVKEY 0x0004
|
||||
#define TLS_FLASH_BLOCK_CA_CERT 0x0005
|
||||
#define TLS_FLASH_BLOCK_ECDH 0x0006
|
||||
#define TLS_FLASH_BLOCK_END 0xFFFF
|
||||
|
||||
/* Flash block header: [id:2 LE][size:2 LE][sha256:32] */
|
||||
#define TLS_FLASH_BLOCK_HEADER_SIZE (2 + 2 + 32)
|
||||
|
||||
/* ECDH key blob offsets */
|
||||
#define TLS_ECDH_BLOB_SIZE 0x90
|
||||
#define TLS_ECDH_X_OFFSET 0x08
|
||||
#define TLS_ECDH_Y_OFFSET 0x4C
|
||||
#define TLS_EC_COORD_SIZE 0x20
|
||||
|
||||
/* Forward declaration */
|
||||
typedef struct _FpiDeviceValidity FpiDeviceValidity;
|
||||
|
||||
/* TLS session state */
|
||||
typedef struct
|
||||
{
|
||||
/* Session keys (derived during handshake) */
|
||||
guint8 sign_key[TLS_AES_KEY_SIZE];
|
||||
guint8 validation_key[TLS_AES_KEY_SIZE];
|
||||
guint8 encryption_key[TLS_AES_KEY_SIZE];
|
||||
guint8 decryption_key[TLS_AES_KEY_SIZE];
|
||||
|
||||
/* Pre-shared keys (derived from hardware identity) */
|
||||
guint8 psk_encryption_key[TLS_AES_KEY_SIZE];
|
||||
guint8 psk_validation_key[TLS_AES_KEY_SIZE];
|
||||
|
||||
/* Handshake state */
|
||||
GChecksum *handshake_hash; /* running SHA-256 of handshake messages */
|
||||
guint8 client_random[TLS_RANDOM_SIZE];
|
||||
guint8 server_random[TLS_RANDOM_SIZE];
|
||||
guint8 master_secret[TLS_MASTER_SECRET_SIZE];
|
||||
|
||||
/* ECDH session ephemeral key pair (generated per handshake) */
|
||||
EVP_PKEY *session_key;
|
||||
|
||||
/* TLS client certificate from flash (block ID 3) */
|
||||
guint8 *tls_cert;
|
||||
gsize tls_cert_len;
|
||||
|
||||
/* Client private key from flash (block ID 4, decrypted with PSK) */
|
||||
EVP_PKEY *priv_key;
|
||||
|
||||
/* ECDH server public key from flash (block ID 6) */
|
||||
EVP_PKEY *ecdh_q;
|
||||
|
||||
/* Raw flash blobs (needed for pairing later) */
|
||||
guint8 *priv_blob;
|
||||
gsize priv_blob_len;
|
||||
guint8 *ecdh_blob;
|
||||
gsize ecdh_blob_len;
|
||||
|
||||
/* TLS channel state */
|
||||
gboolean secure_rx;
|
||||
gboolean secure_tx;
|
||||
gboolean keys_loaded;
|
||||
|
||||
/* Runtime data keys (populated from data store during open) */
|
||||
const guint8 *password; /* TLS password (32 bytes) */
|
||||
gsize password_len;
|
||||
const guint8 *gwk_sign; /* GWK signing key (32 bytes) */
|
||||
gsize gwk_sign_len;
|
||||
const guint8 *fw_pubkey_x; /* firmware ECDSA public key X (32 bytes) */
|
||||
gsize fw_pubkey_x_len;
|
||||
const guint8 *fw_pubkey_y; /* firmware ECDSA public key Y (32 bytes) */
|
||||
gsize fw_pubkey_y_len;
|
||||
} ValidityTlsState;
|
||||
|
||||
/* TLS handshake SSM states */
|
||||
typedef enum {
|
||||
TLS_HS_SEND_CLIENT_HELLO = 0,
|
||||
TLS_HS_RECV_SERVER_HELLO,
|
||||
TLS_HS_SEND_CLIENT_FINISH,
|
||||
TLS_HS_RECV_SERVER_FINISH,
|
||||
TLS_HS_PARSE_SERVER_FINISH,
|
||||
TLS_HS_NUM_STATES,
|
||||
} ValidityTlsHandshakeState;
|
||||
|
||||
/* TLS flash read SSM states */
|
||||
typedef enum {
|
||||
TLS_FLASH_READ_CMD = 0,
|
||||
TLS_FLASH_READ_RECV,
|
||||
TLS_FLASH_READ_NUM_STATES,
|
||||
} ValidityTlsFlashReadState;
|
||||
|
||||
/* ---- Public API ---- */
|
||||
|
||||
void validity_tls_init (ValidityTlsState *tls);
|
||||
void validity_tls_free (ValidityTlsState *tls);
|
||||
|
||||
void validity_tls_derive_psk (ValidityTlsState *tls);
|
||||
|
||||
gboolean validity_tls_parse_flash (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* PRF — exported for testing */
|
||||
void validity_tls_prf (const guint8 *secret,
|
||||
gsize secret_len,
|
||||
const guint8 *seed,
|
||||
gsize seed_len,
|
||||
guint8 *output,
|
||||
gsize output_len);
|
||||
|
||||
/* Encrypt/decrypt for TLS app data */
|
||||
guint8 *validity_tls_encrypt (ValidityTlsState *tls,
|
||||
const guint8 *plaintext,
|
||||
gsize plaintext_len,
|
||||
gsize *out_len);
|
||||
|
||||
guint8 *validity_tls_decrypt (ValidityTlsState *tls,
|
||||
const guint8 *ciphertext,
|
||||
gsize ciphertext_len,
|
||||
gsize *out_len,
|
||||
GError **error);
|
||||
|
||||
/* Build TLS app_data record wrapping a VCSFW command */
|
||||
guint8 *validity_tls_wrap_app_data (ValidityTlsState *tls,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse TLS response, returning decrypted app_data */
|
||||
guint8 *validity_tls_unwrap_response (ValidityTlsState *tls,
|
||||
const guint8 *response,
|
||||
gsize response_len,
|
||||
gsize *out_len,
|
||||
GError **error);
|
||||
|
||||
/* Build the full handshake USB commands.
|
||||
* Returns the first USB command (ClientHello with 0x44000000 prefix). */
|
||||
guint8 *validity_tls_build_client_hello (ValidityTlsState *tls,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse ServerHello + CertReq + ServerHelloDone from USB response */
|
||||
gboolean validity_tls_parse_server_hello (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* Build Certificate + ClientKeyExchange + CertVerify + ChangeCipherSpec + Finished */
|
||||
guint8 *validity_tls_build_client_finish (ValidityTlsState *tls,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse server ChangeCipherSpec + Finished */
|
||||
gboolean validity_tls_parse_server_finish (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* SSM runner for TLS handshake */
|
||||
void validity_tls_handshake_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
/* SSM runner for reading flash TLS data */
|
||||
void validity_tls_flash_read_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
1270
libfprint/drivers/validity/validity_verify.c
Normal file
1270
libfprint/drivers/validity/validity_verify.c
Normal file
File diff suppressed because it is too large
Load diff
392
libfprint/drivers/validity/vcsfw_protocol.c
Normal file
392
libfprint/drivers/validity/vcsfw_protocol.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* VCSFW protocol implementation for Validity/Synaptics fingerprint sensors
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "validity"
|
||||
|
||||
#include "drivers_api.h"
|
||||
#include "validity_pack.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
/* ---- VcsfwCmdData lifecycle ---- */
|
||||
|
||||
VcsfwCmdData *
|
||||
vcsfw_cmd_data_new (const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback)
|
||||
{
|
||||
VcsfwCmdData *data = g_new0 (VcsfwCmdData, 1);
|
||||
|
||||
data->cmd_data = g_memdup2 (cmd, cmd_len);
|
||||
data->cmd_len = cmd_len;
|
||||
data->callback = callback;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void
|
||||
vcsfw_cmd_data_free (gpointer data)
|
||||
{
|
||||
VcsfwCmdData *cmd_data = data;
|
||||
|
||||
if (cmd_data == NULL)
|
||||
return;
|
||||
|
||||
g_free (cmd_data->cmd_data);
|
||||
g_free (cmd_data);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_receive_cb (FpiUsbTransfer *transfer,
|
||||
FpDevice *device,
|
||||
gpointer user_data,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
||||
VcsfwCmdData *cmd_data = fpi_ssm_get_data (transfer->ssm);
|
||||
guint16 status;
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (cmd_data->callback)
|
||||
cmd_data->callback (self, NULL, 0, 0, error);
|
||||
else
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (transfer->actual_length < 2)
|
||||
{
|
||||
g_warning ("VCSFW response too short: %" G_GSSIZE_FORMAT " bytes",
|
||||
transfer->actual_length);
|
||||
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
|
||||
if (cmd_data->callback)
|
||||
cmd_data->callback (self, NULL, 0, 0, error);
|
||||
else
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
validity_unpack (transfer->buffer, transfer->actual_length, "h", &status);
|
||||
|
||||
fp_dbg ("VCSFW response: status=0x%04x, len=%" G_GSSIZE_FORMAT,
|
||||
status, transfer->actual_length - 2);
|
||||
|
||||
/* Stash status and data for direct access in RECV states */
|
||||
self->cmd_response_status = status;
|
||||
g_clear_pointer (&self->cmd_response_data, g_free);
|
||||
if (transfer->actual_length > 2)
|
||||
{
|
||||
self->cmd_response_len = transfer->actual_length - 2;
|
||||
self->cmd_response_data = g_memdup2 (transfer->buffer + 2,
|
||||
self->cmd_response_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->cmd_response_len = 0;
|
||||
self->cmd_response_data = NULL;
|
||||
}
|
||||
|
||||
if (cmd_data->callback)
|
||||
{
|
||||
cmd_data->callback (self,
|
||||
self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
status,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/* If the callback didn't fail the SSM, advance it */
|
||||
fpi_ssm_mark_completed (transfer->ssm);
|
||||
}
|
||||
|
||||
void
|
||||
vcsfw_cmd_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
{
|
||||
VcsfwCmdData *cmd_data = fpi_ssm_get_data (ssm);
|
||||
FpiUsbTransfer *transfer;
|
||||
|
||||
switch (fpi_ssm_get_cur_state (ssm))
|
||||
{
|
||||
case VCSFW_CMD_STATE_SEND:
|
||||
fp_dbg ("VCSFW send cmd 0x%02x, len=%" G_GSIZE_FORMAT,
|
||||
cmd_data->cmd_data[0], cmd_data->cmd_len);
|
||||
|
||||
transfer = fpi_usb_transfer_new (dev);
|
||||
transfer->short_is_error = TRUE;
|
||||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT,
|
||||
cmd_data->cmd_len);
|
||||
memcpy (transfer->buffer, cmd_data->cmd_data, cmd_data->cmd_len);
|
||||
transfer->ssm = ssm;
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
|
||||
fpi_ssm_usb_transfer_cb, NULL);
|
||||
break;
|
||||
|
||||
case VCSFW_CMD_STATE_RECV:
|
||||
transfer = fpi_usb_transfer_new (dev);
|
||||
transfer->ssm = ssm;
|
||||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
|
||||
VALIDITY_MAX_TRANSFER_LEN);
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
|
||||
cmd_receive_cb, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_ssm_done (FpiSsm *ssm,
|
||||
FpDevice *dev,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
self->cmd_ssm = NULL;
|
||||
|
||||
if (error)
|
||||
fp_dbg ("VCSFW command SSM failed: %s", error->message);
|
||||
|
||||
/* Error is handled by the callback, nothing else to do here */
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
void
|
||||
vcsfw_cmd_send (FpiDeviceValidity *self,
|
||||
FpiSsm *parent_ssm,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback)
|
||||
{
|
||||
FpiSsm *ssm;
|
||||
VcsfwCmdData *cmd_data;
|
||||
|
||||
cmd_data = vcsfw_cmd_data_new (cmd, cmd_len, callback);
|
||||
|
||||
ssm = fpi_ssm_new (FP_DEVICE (self), vcsfw_cmd_run_state,
|
||||
VCSFW_CMD_STATE_NUM_STATES);
|
||||
fpi_ssm_set_data (ssm, cmd_data, vcsfw_cmd_data_free);
|
||||
|
||||
self->cmd_ssm = ssm;
|
||||
|
||||
if (parent_ssm)
|
||||
fpi_ssm_start_subsm (parent_ssm, ssm);
|
||||
else
|
||||
fpi_ssm_start (ssm, cmd_ssm_done);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* TLS-wrapped command/response exchange
|
||||
*
|
||||
* Same pattern as vcsfw_cmd_send but wraps the outgoing command in
|
||||
* a TLS application data record (with 0x44000000 prefix) and
|
||||
* decrypts the incoming response before extracting the 2-byte
|
||||
* VCSFW status.
|
||||
* ================================================================ */
|
||||
|
||||
static void
|
||||
tls_cmd_receive_cb (FpiUsbTransfer *transfer,
|
||||
FpDevice *device,
|
||||
gpointer user_data,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
||||
VcsfwCmdData *cmd_data = fpi_ssm_get_data (transfer->ssm);
|
||||
guint16 status;
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (cmd_data->callback)
|
||||
cmd_data->callback (self, NULL, 0, 0, error);
|
||||
else
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decrypt TLS app data response */
|
||||
{
|
||||
GString *hex = g_string_new ("VCSFW TLS raw response: ");
|
||||
for (gsize i = 0; i < MIN ((gsize) transfer->actual_length, (gsize) 40); i++)
|
||||
g_string_append_printf (hex, "%02x ", transfer->buffer[i]);
|
||||
if (transfer->actual_length > 40)
|
||||
g_string_append_printf (hex, "... (%" G_GSSIZE_FORMAT " total)",
|
||||
transfer->actual_length);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
gsize decrypted_len;
|
||||
guint8 *decrypted = validity_tls_unwrap_response (
|
||||
&self->tls,
|
||||
transfer->buffer, transfer->actual_length,
|
||||
&decrypted_len, &error);
|
||||
|
||||
if (!decrypted)
|
||||
{
|
||||
if (cmd_data->callback)
|
||||
cmd_data->callback (self, NULL, 0, 0, error);
|
||||
else
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decrypted data is VCSFW response: status(2) + payload */
|
||||
if (decrypted_len < 2)
|
||||
{
|
||||
g_free (decrypted);
|
||||
error = fpi_device_error_new (FP_DEVICE_ERROR_PROTO);
|
||||
if (cmd_data->callback)
|
||||
cmd_data->callback (self, NULL, 0, 0, error);
|
||||
else
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
validity_unpack (decrypted, decrypted_len, "h", &status);
|
||||
|
||||
fp_dbg ("VCSFW TLS response: status=0x%04x, len=%" G_GSIZE_FORMAT,
|
||||
status, decrypted_len - 2);
|
||||
|
||||
/* Stash for parent SSM consumption */
|
||||
self->cmd_response_status = status;
|
||||
g_clear_pointer (&self->cmd_response_data, g_free);
|
||||
if (decrypted_len > 2)
|
||||
{
|
||||
self->cmd_response_len = decrypted_len - 2;
|
||||
self->cmd_response_data = g_memdup2 (decrypted + 2,
|
||||
self->cmd_response_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->cmd_response_len = 0;
|
||||
self->cmd_response_data = NULL;
|
||||
}
|
||||
|
||||
g_free (decrypted);
|
||||
|
||||
if (cmd_data->callback)
|
||||
{
|
||||
cmd_data->callback (self,
|
||||
self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
status,
|
||||
NULL);
|
||||
}
|
||||
|
||||
fpi_ssm_mark_completed (transfer->ssm);
|
||||
}
|
||||
|
||||
void
|
||||
vcsfw_tls_cmd_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
VcsfwCmdData *cmd_data = fpi_ssm_get_data (ssm);
|
||||
FpiUsbTransfer *transfer;
|
||||
|
||||
switch (fpi_ssm_get_cur_state (ssm))
|
||||
{
|
||||
case VCSFW_TLS_CMD_STATE_SEND:
|
||||
{
|
||||
/* Wrap VCSFW command as TLS application data */
|
||||
gsize wrapped_len;
|
||||
guint8 *wrapped = validity_tls_wrap_app_data (&self->tls,
|
||||
cmd_data->cmd_data,
|
||||
cmd_data->cmd_len,
|
||||
&wrapped_len);
|
||||
|
||||
/* Build USB payload: TLS record directly (no 0x44000000 prefix
|
||||
* for post-handshake app data, per python-validity) */
|
||||
gsize usb_len = wrapped_len;
|
||||
|
||||
fp_dbg ("VCSFW TLS send cmd 0x%02x, plaintext=%" G_GSIZE_FORMAT
|
||||
", wire=%" G_GSIZE_FORMAT,
|
||||
cmd_data->cmd_data[0], cmd_data->cmd_len, usb_len);
|
||||
|
||||
transfer = fpi_usb_transfer_new (dev);
|
||||
transfer->short_is_error = TRUE;
|
||||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, usb_len);
|
||||
memcpy (transfer->buffer, wrapped, wrapped_len);
|
||||
g_free (wrapped);
|
||||
|
||||
transfer->ssm = ssm;
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
|
||||
fpi_ssm_usb_transfer_cb, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case VCSFW_TLS_CMD_STATE_RECV:
|
||||
transfer = fpi_usb_transfer_new (dev);
|
||||
transfer->ssm = ssm;
|
||||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
|
||||
VALIDITY_MAX_TRANSFER_LEN);
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
|
||||
tls_cmd_receive_cb, NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
vcsfw_tls_cmd_send (FpiDeviceValidity *self,
|
||||
FpiSsm *parent_ssm,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback)
|
||||
{
|
||||
FpiSsm *ssm;
|
||||
VcsfwCmdData *cmd_data;
|
||||
|
||||
cmd_data = vcsfw_cmd_data_new (cmd, cmd_len, callback);
|
||||
|
||||
ssm = fpi_ssm_new (FP_DEVICE (self), vcsfw_tls_cmd_run_state,
|
||||
VCSFW_TLS_CMD_STATE_NUM_STATES);
|
||||
fpi_ssm_set_data (ssm, cmd_data, vcsfw_cmd_data_free);
|
||||
|
||||
self->cmd_ssm = ssm;
|
||||
|
||||
if (parent_ssm)
|
||||
fpi_ssm_start_subsm (parent_ssm, ssm);
|
||||
else
|
||||
fpi_ssm_start (ssm, cmd_ssm_done);
|
||||
}
|
||||
|
||||
/* ---- GET_VERSION (cmd 0x01) response parser ---- */
|
||||
|
||||
gboolean
|
||||
vcsfw_parse_version (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityVersionInfo *info)
|
||||
{
|
||||
const guint8 *serial;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (info != NULL, FALSE);
|
||||
|
||||
if (!validity_unpack (data, data_len, "wwbbbbbbbbdhbb",
|
||||
&info->build_time, &info->build_num,
|
||||
&info->version_major, &info->version_minor,
|
||||
&info->target, &info->product,
|
||||
&info->silicon_rev, &info->formal_release,
|
||||
&info->platform, &info->patch,
|
||||
&serial, (gsize) sizeof (info->serial_number),
|
||||
&info->security,
|
||||
&info->iface, &info->device_type))
|
||||
return FALSE;
|
||||
|
||||
memcpy (info->serial_number, serial, sizeof (info->serial_number));
|
||||
return TRUE;
|
||||
}
|
||||
131
libfprint/drivers/validity/vcsfw_protocol.h
Normal file
131
libfprint/drivers/validity/vcsfw_protocol.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* VCSFW protocol definitions for Validity/Synaptics fingerprint sensors
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "validity.h"
|
||||
|
||||
/* ---- VCSFW Command IDs (pre-TLS) ---- */
|
||||
#define VCSFW_CMD_GET_VERSION 0x01
|
||||
#define VCSFW_CMD_READ_HW_REG32 0x07
|
||||
#define VCSFW_CMD_WRITE_HW_REG32 0x08
|
||||
#define VCSFW_CMD_UNKNOWN_INIT 0x19
|
||||
#define VCSFW_CMD_GET_FLASH_INFO 0x3E
|
||||
#define VCSFW_CMD_READ_FLASH 0x40
|
||||
#define VCSFW_CMD_WRITE_FLASH 0x41
|
||||
#define VCSFW_CMD_WRITE_FW_SIG 0x42
|
||||
#define VCSFW_CMD_GET_FW_INFO 0x43
|
||||
#define VCSFW_CMD_PARTITION_FLASH 0x4F
|
||||
|
||||
/* ---- VCSFW Command IDs (post-TLS, via tls.app()) ---- */
|
||||
#define VCSFW_CMD_CAPTURE 0x02
|
||||
#define VCSFW_CMD_CAPTURE_STOP 0x04
|
||||
#define VCSFW_CMD_GLOW_START 0x39
|
||||
#define VCSFW_CMD_ERASE_FLASH 0x3F
|
||||
#define VCSFW_CMD_DB_INFO 0x45
|
||||
#define VCSFW_CMD_GET_RECORD_CHILDREN 0x46
|
||||
#define VCSFW_CMD_NEW_RECORD 0x47
|
||||
#define VCSFW_CMD_DEL_RECORD 0x48
|
||||
#define VCSFW_CMD_GET_RECORD_VALUE 0x49
|
||||
#define VCSFW_CMD_GET_USER 0x4A
|
||||
#define VCSFW_CMD_GET_USER_STORAGE 0x4B
|
||||
#define VCSFW_CMD_GET_PRG_STATUS 0x51
|
||||
#define VCSFW_CMD_MATCH_FINGER 0x5E
|
||||
#define VCSFW_CMD_GET_MATCH_RESULT 0x60
|
||||
#define VCSFW_CMD_MATCH_CLEANUP 0x62
|
||||
#define VCSFW_CMD_ENROLLMENT_UPDATE_START 0x68
|
||||
#define VCSFW_CMD_CREATE_ENROLLMENT 0x69
|
||||
#define VCSFW_CMD_ENROLLMENT_UPDATE 0x6B
|
||||
#define VCSFW_CMD_GET_FACTORY_BITS 0x6F
|
||||
#define VCSFW_CMD_IDENTIFY_SENSOR 0x75
|
||||
|
||||
/* ---- VCSFW Response Status Codes ---- */
|
||||
#define VCSFW_STATUS_OK 0x0000
|
||||
#define VCSFW_STATUS_NO_FW 0xB004
|
||||
|
||||
/* ---- Callback types ---- */
|
||||
|
||||
/**
|
||||
* VcsfwCmdCallback:
|
||||
* @self: the validity device
|
||||
* @data: response data (after 2-byte status, NULL on error)
|
||||
* @data_len: length of response data (excluding 2-byte status)
|
||||
* @status: the 2-byte VCSFW status code
|
||||
* @error: a GError if the transfer failed, or NULL
|
||||
*
|
||||
* Callback invoked after a VCSFW command/response exchange completes.
|
||||
*/
|
||||
typedef void (*VcsfwCmdCallback) (FpiDeviceValidity *self,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
guint16 status,
|
||||
GError *error);
|
||||
|
||||
/* ---- Command/response SSM states ---- */
|
||||
typedef enum {
|
||||
VCSFW_CMD_STATE_SEND = 0,
|
||||
VCSFW_CMD_STATE_RECV,
|
||||
VCSFW_CMD_STATE_NUM_STATES,
|
||||
} VcsfwCmdSsmState;
|
||||
|
||||
/* ---- TLS-wrapped command/response SSM states ---- */
|
||||
typedef enum {
|
||||
VCSFW_TLS_CMD_STATE_SEND = 0,
|
||||
VCSFW_TLS_CMD_STATE_RECV,
|
||||
VCSFW_TLS_CMD_STATE_NUM_STATES,
|
||||
} VcsfwTlsCmdSsmState;
|
||||
|
||||
/* ---- Context for a single command/response exchange ---- */
|
||||
typedef struct
|
||||
{
|
||||
guint8 *cmd_data;
|
||||
gsize cmd_len;
|
||||
VcsfwCmdCallback callback;
|
||||
} VcsfwCmdData;
|
||||
|
||||
/* ---- Functions ---- */
|
||||
|
||||
void vcsfw_cmd_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
VcsfwCmdData *vcsfw_cmd_data_new (const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback);
|
||||
|
||||
void vcsfw_cmd_data_free (gpointer data);
|
||||
|
||||
void vcsfw_cmd_send (FpiDeviceValidity *self,
|
||||
FpiSsm *parent_ssm,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback);
|
||||
|
||||
void vcsfw_tls_cmd_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
void vcsfw_tls_cmd_send (FpiDeviceValidity *self,
|
||||
FpiSsm *parent_ssm,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
VcsfwCmdCallback callback);
|
||||
|
||||
gboolean vcsfw_parse_version (const guint8 *data,
|
||||
gsize data_len,
|
||||
ValidityVersionInfo *info);
|
||||
|
|
@ -62,7 +62,6 @@ static const FpIdEntry allowlist_id_table[] = {
|
|||
{ .vid = 0x06cb, .pid = 0x0081 },
|
||||
{ .vid = 0x06cb, .pid = 0x0088 },
|
||||
{ .vid = 0x06cb, .pid = 0x008a },
|
||||
{ .vid = 0x06cb, .pid = 0x009a },
|
||||
{ .vid = 0x06cb, .pid = 0x009b },
|
||||
{ .vid = 0x06cb, .pid = 0x00a1 },
|
||||
{ .vid = 0x06cb, .pid = 0x00a2 },
|
||||
|
|
@ -110,11 +109,8 @@ static const FpIdEntry allowlist_id_table[] = {
|
|||
{ .vid = 0x138a, .pid = 0x003c },
|
||||
{ .vid = 0x138a, .pid = 0x003d },
|
||||
{ .vid = 0x138a, .pid = 0x003f },
|
||||
{ .vid = 0x138a, .pid = 0x0090 },
|
||||
{ .vid = 0x138a, .pid = 0x0092 },
|
||||
{ .vid = 0x138a, .pid = 0x0094 },
|
||||
{ .vid = 0x138a, .pid = 0x0097 },
|
||||
{ .vid = 0x138a, .pid = 0x009d },
|
||||
{ .vid = 0x138a, .pid = 0x00ab },
|
||||
{ .vid = 0x138a, .pid = 0x00a6 },
|
||||
{ .vid = 0x147e, .pid = 0x1002 },
|
||||
|
|
|
|||
|
|
@ -153,6 +153,19 @@ driver_sources = {
|
|||
[ 'drivers/realtek/realtek.c' ],
|
||||
'focaltech_moc' :
|
||||
[ 'drivers/focaltech_moc/focaltech_moc.c' ],
|
||||
'validity' :
|
||||
[ 'drivers/validity/validity.c',
|
||||
'drivers/validity/vcsfw_protocol.c',
|
||||
'drivers/validity/validity_tls.c',
|
||||
'drivers/validity/validity_fwext.c',
|
||||
'drivers/validity/validity_sensor.c',
|
||||
'drivers/validity/validity_capture.c',
|
||||
'drivers/validity/validity_db.c',
|
||||
'drivers/validity/validity_enroll.c',
|
||||
'drivers/validity/validity_verify.c',
|
||||
'drivers/validity/validity_hal.c',
|
||||
'drivers/validity/validity_pair.c',
|
||||
'drivers/validity/validity_data.c' ],
|
||||
}
|
||||
|
||||
helper_sources = {
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ default_drivers = [
|
|||
'fpcmoc',
|
||||
'realtek',
|
||||
'focaltech_moc',
|
||||
'validity',
|
||||
]
|
||||
|
||||
spi_drivers = [
|
||||
|
|
@ -178,6 +179,7 @@ endian_independent_drivers = virtual_drivers + [
|
|||
'vcom5s',
|
||||
'vfs101',
|
||||
'vfs7552',
|
||||
'validity',
|
||||
]
|
||||
|
||||
all_drivers = default_drivers + virtual_drivers
|
||||
|
|
@ -210,6 +212,7 @@ driver_helper_mapping = {
|
|||
'aes3500' : [ 'aeslib', 'aes3k' ],
|
||||
'aes4000' : [ 'aeslib', 'aes3k' ],
|
||||
'uru4000' : [ 'openssl' ],
|
||||
'validity' : [ 'openssl' ],
|
||||
'elanspi' : [ 'udev' ],
|
||||
'virtual_image' : [ 'virtual' ],
|
||||
'virtual_device' : [ 'virtual' ],
|
||||
|
|
|
|||
|
|
@ -22,6 +22,15 @@ for m in devices_re.finditer(data):
|
|||
pid = m.group(2)
|
||||
devices.append((vid, pid))
|
||||
|
||||
# TODO:remove when the devices are removed from the wiki unsupported list.
|
||||
allow = []
|
||||
allow.append(("06cb", "009a"))
|
||||
allow.append(("138a", "0090"))
|
||||
allow.append(("138a", "0097"))
|
||||
allow.append(("138a", "009d"))
|
||||
|
||||
devices = [d for d in devices if d not in allow]
|
||||
|
||||
generator = open(os.path.join(os.path.dirname(__file__), '..', 'libfprint', 'fprint-list-udev-hwdb.c')).read()
|
||||
|
||||
id_re = re.compile(' { .vid = 0x([a-fA-F0-9]*), .pid = 0x([a-fA-F0-9]*) }')
|
||||
|
|
|
|||
|
|
@ -33,3 +33,19 @@
|
|||
...
|
||||
fun:libusb_init_context
|
||||
}
|
||||
|
||||
{
|
||||
<ignore-openssl-sha256-stack-uninitialised-value8>
|
||||
Memcheck:Value8
|
||||
fun:gchecksum_digest_to_string
|
||||
fun:*sha256*
|
||||
fun:g_checksum_get_digest
|
||||
}
|
||||
|
||||
{
|
||||
<ignore-openssl-sha256-stack-uninitialised-cond>
|
||||
Memcheck:Cond
|
||||
fun:gchecksum_digest_to_string
|
||||
fun:*sha256*
|
||||
fun:g_checksum_get_digest
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ drivers_tests = [
|
|||
'realtek',
|
||||
'realtek-5816',
|
||||
'focaltech_moc',
|
||||
'validity',
|
||||
]
|
||||
|
||||
if get_option('introspection')
|
||||
|
|
@ -321,6 +322,26 @@ foreach test_name: unit_tests
|
|||
)
|
||||
endforeach
|
||||
|
||||
# Validity unit tests (needs driver library + OpenSSL)
|
||||
if 'validity' in supported_drivers
|
||||
openssl_dep = dependency('openssl', version: '>= 3.0', required: false)
|
||||
if openssl_dep.found()
|
||||
validity_test = executable('test-validity',
|
||||
sources: 'test-validity.c',
|
||||
dependencies: [ libfprint_private_dep, openssl_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
link_whole: test_utils,
|
||||
install: false,
|
||||
)
|
||||
test('validity',
|
||||
validity_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
|
||||
# Run udev rule generator with fatal warnings
|
||||
envs.set('UDEV_HWDB', udev_hwdb.full_path())
|
||||
envs.set('UDEV_HWDB_CHECK_CONTENTS', default_drivers_are_enabled ? '1' : '0')
|
||||
|
|
|
|||
5748
tests/test-validity.c
Normal file
5748
tests/test-validity.c
Normal file
File diff suppressed because it is too large
Load diff
BIN
tests/validity/custom.pcapng
Normal file
BIN
tests/validity/custom.pcapng
Normal file
Binary file not shown.
103
tests/validity/custom.py
Normal file
103
tests/validity/custom.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
import gi
|
||||
|
||||
gi.require_version('FPrint', '2.0')
|
||||
from gi.repository import FPrint, GLib
|
||||
|
||||
# Exit with error on any exception, including those happening in callbacks
|
||||
sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))
|
||||
|
||||
ctx = GLib.main_context_default()
|
||||
|
||||
c = FPrint.Context()
|
||||
c.enumerate()
|
||||
devices = c.get_devices()
|
||||
|
||||
assert len(devices) == 1, f"Expected 1 device, got {len(devices)}"
|
||||
|
||||
d = devices[0]
|
||||
del devices
|
||||
|
||||
# Verify driver name
|
||||
assert d.get_driver() == "validity", f"Expected 'validity', got '{d.get_driver()}'"
|
||||
|
||||
# Verify features detected by auto_initialize_features
|
||||
assert not d.has_feature(FPrint.DeviceFeature.CAPTURE)
|
||||
assert d.has_feature(FPrint.DeviceFeature.VERIFY)
|
||||
assert d.has_feature(FPrint.DeviceFeature.IDENTIFY)
|
||||
assert not 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)
|
||||
assert d.has_feature(FPrint.DeviceFeature.ALWAYS_ON)
|
||||
|
||||
d.open_sync()
|
||||
|
||||
# 1. Clear storage — ensure the sensor is in a clean state
|
||||
print("clearing storage")
|
||||
d.clear_storage_sync()
|
||||
print("clear done")
|
||||
|
||||
# 2. Enroll a finger
|
||||
template = FPrint.Print.new(d)
|
||||
|
||||
def enroll_progress(*args):
|
||||
print('enroll progress: ' + str(args))
|
||||
|
||||
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")
|
||||
|
||||
# 3. List enrolled prints — should have exactly one
|
||||
print("listing")
|
||||
stored = d.list_prints_sync()
|
||||
print("listing done")
|
||||
assert len(stored) == 1
|
||||
|
||||
# 4. Verify against the enrolled print
|
||||
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
|
||||
|
||||
# 5. Identify (async) with deserialized prints
|
||||
identified = False
|
||||
|
||||
def identify_done(dev, res):
|
||||
global identified
|
||||
identified = True
|
||||
identify_match, identify_print = dev.identify_finish(res)
|
||||
print('identification done: ', identify_match, identify_print)
|
||||
assert identify_match.equal(identify_print)
|
||||
|
||||
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)
|
||||
|
||||
# 6. Delete the enrolled print
|
||||
print("deleting")
|
||||
d.delete_print_sync(p)
|
||||
print("delete done")
|
||||
|
||||
d.close_sync()
|
||||
|
||||
del d
|
||||
del c
|
||||
264
tests/validity/device
Normal file
264
tests/validity/device
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
P: /devices/pci0000:00/0000:00:14.0/usb1/1-9
|
||||
N: bus/usb/001/006=12010002FF10FF08CB069A0064010000010109023500010100A0320904000005FF000000070501024000000705810240000007058202400000070583030800040705840310000A
|
||||
E: BUSNUM=001
|
||||
E: DEVNAME=/dev/bus/usb/001/006
|
||||
E: DEVNUM=006
|
||||
E: DEVTYPE=usb_device
|
||||
E: DRIVER=usb
|
||||
E: ID_AUTOSUSPEND=1
|
||||
E: ID_BUS=usb
|
||||
E: ID_INTEGRATION=internal
|
||||
E: ID_MODEL=009a
|
||||
E: ID_MODEL_ENC=009a
|
||||
E: ID_MODEL_FROM_DATABASE=Metallica MIS Touch Fingerprint Reader
|
||||
E: ID_MODEL_ID=009a
|
||||
E: ID_PATH=pci-0000:00:14.0-usb-0:9
|
||||
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_9
|
||||
E: ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:9
|
||||
E: ID_PERSIST=0
|
||||
E: ID_REVISION=0164
|
||||
E: ID_SERIAL=06cb_009a_c7e2948627cb
|
||||
E: ID_SERIAL_SHORT=c7e2948627cb
|
||||
E: ID_USB_INTERFACES=:ff0000:
|
||||
E: ID_USB_MODEL=009a
|
||||
E: ID_USB_MODEL_ENC=009a
|
||||
E: ID_USB_MODEL_ID=009a
|
||||
E: ID_USB_REVISION=0164
|
||||
E: ID_USB_SERIAL=06cb_009a_c7e2948627cb
|
||||
E: ID_USB_SERIAL_SHORT=c7e2948627cb
|
||||
E: ID_USB_VENDOR=06cb
|
||||
E: ID_USB_VENDOR_ENC=06cb
|
||||
E: ID_USB_VENDOR_ID=06cb
|
||||
E: ID_VENDOR=06cb
|
||||
E: ID_VENDOR_ENC=06cb
|
||||
E: ID_VENDOR_FROM_DATABASE=Synaptics, Inc.
|
||||
E: ID_VENDOR_ID=06cb
|
||||
E: MAJOR=189
|
||||
E: MINOR=5
|
||||
E: PRODUCT=6cb/9a/164
|
||||
E: SUBSYSTEM=usb
|
||||
E: TYPE=255/16/255
|
||||
A: authorized=1\n
|
||||
A: avoid_reset_quirk=0\n
|
||||
A: bConfigurationValue=1\n
|
||||
A: bDeviceClass=ff\n
|
||||
A: bDeviceProtocol=ff\n
|
||||
A: bDeviceSubClass=10\n
|
||||
A: bMaxPacketSize0=8\n
|
||||
A: bMaxPower=100mA\n
|
||||
A: bNumConfigurations=1\n
|
||||
A: bNumInterfaces= 1\n
|
||||
A: bcdDevice=0164\n
|
||||
A: bmAttributes=a0\n
|
||||
A: busnum=1\n
|
||||
A: configuration=
|
||||
H: descriptors=12010002FF10FF08CB069A0064010000010109023500010100A0320904000005FF000000070501024000000705810240000007058202400000070583030800040705840310000A
|
||||
A: dev=189:5\n
|
||||
A: devnum=6\n
|
||||
A: devpath=9\n
|
||||
L: driver=../../../../../bus/usb/drivers/usb
|
||||
L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:1d/device:1e/device:29
|
||||
A: idProduct=009a\n
|
||||
A: idVendor=06cb\n
|
||||
A: ltm_capable=no\n
|
||||
A: maxchild=0\n
|
||||
A: physical_location/dock=no\n
|
||||
A: physical_location/horizontal_position=left\n
|
||||
A: physical_location/lid=no\n
|
||||
A: physical_location/panel=unknown\n
|
||||
A: physical_location/vertical_position=upper\n
|
||||
L: port=../1-0:1.0/usb1-port9
|
||||
A: power/active_duration=39041\n
|
||||
A: power/autosuspend=2\n
|
||||
A: power/autosuspend_delay_ms=2000\n
|
||||
A: power/connected_duration=8463727\n
|
||||
A: power/control=auto\n
|
||||
A: power/level=auto\n
|
||||
A: power/persist=1\n
|
||||
A: power/runtime_active_time=39927\n
|
||||
A: power/runtime_status=active\n
|
||||
A: power/runtime_suspended_time=8423564\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: quirks=0x0\n
|
||||
A: removable=fixed\n
|
||||
A: rx_lanes=1\n
|
||||
A: serial=c7e2948627cb\n
|
||||
A: speed=12\n
|
||||
A: tx_lanes=1\n
|
||||
A: urbnum=124\n
|
||||
A: version= 2.00\n
|
||||
|
||||
P: /devices/pci0000:00/0000:00:14.0/usb1
|
||||
N: bus/usb/001/001=12010002090001406B1D020019060302010109021900010100E0000904000001090000000705810304000C
|
||||
E: BUSNUM=001
|
||||
E: CURRENT_TAGS=:seat:
|
||||
E: DEVNAME=/dev/bus/usb/001/001
|
||||
E: DEVNUM=001
|
||||
E: DEVTYPE=usb_device
|
||||
E: DRIVER=usb
|
||||
E: ID_AUTOSUSPEND=1
|
||||
E: ID_BUS=usb
|
||||
E: ID_FOR_SEAT=usb-pci-0000_00_14_0
|
||||
E: ID_MODEL=xHCI_Host_Controller
|
||||
E: ID_MODEL_ENC=xHCI\x20Host\x20Controller
|
||||
E: ID_MODEL_FROM_DATABASE=2.0 root hub
|
||||
E: ID_MODEL_ID=0002
|
||||
E: ID_PATH=pci-0000:00:14.0
|
||||
E: ID_PATH_TAG=pci-0000_00_14_0
|
||||
E: ID_REVISION=0619
|
||||
E: ID_SERIAL=Linux_6.19.10-zen1-1-zen_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
|
||||
E: ID_SERIAL_SHORT=0000:00:14.0
|
||||
E: ID_USB_INTERFACES=:090000:
|
||||
E: ID_USB_MODEL=xHCI_Host_Controller
|
||||
E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller
|
||||
E: ID_USB_MODEL_ID=0002
|
||||
E: ID_USB_REVISION=0619
|
||||
E: ID_USB_SERIAL=Linux_6.19.10-zen1-1-zen_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
|
||||
E: ID_USB_SERIAL_SHORT=0000:00:14.0
|
||||
E: ID_USB_VENDOR=Linux_6.19.10-zen1-1-zen_xhci-hcd
|
||||
E: ID_USB_VENDOR_ENC=Linux\x206.19.10-zen1-1-zen\x20xhci-hcd
|
||||
E: ID_USB_VENDOR_ID=1d6b
|
||||
E: ID_VENDOR=Linux_6.19.10-zen1-1-zen_xhci-hcd
|
||||
E: ID_VENDOR_ENC=Linux\x206.19.10-zen1-1-zen\x20xhci-hcd
|
||||
E: ID_VENDOR_FROM_DATABASE=Linux Foundation
|
||||
E: ID_VENDOR_ID=1d6b
|
||||
E: MAJOR=189
|
||||
E: MINOR=0
|
||||
E: PRODUCT=1d6b/2/619
|
||||
E: SUBSYSTEM=usb
|
||||
E: TAGS=:seat:
|
||||
E: TYPE=9/0/1
|
||||
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=0619\n
|
||||
A: bmAttributes=e0\n
|
||||
A: busnum=1\n
|
||||
A: configuration=
|
||||
H: descriptors=12010002090001406B1D020019060302010109021900010100E0000904000001090000000705810304000C
|
||||
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:1d/device:1e
|
||||
A: idProduct=0002\n
|
||||
A: idVendor=1d6b\n
|
||||
A: interface_authorized_default=1\n
|
||||
A: ltm_capable=no\n
|
||||
A: manufacturer=Linux 6.19.10-zen1-1-zen xhci-hcd\n
|
||||
A: maxchild=12\n
|
||||
A: power/active_duration=8464967\n
|
||||
A: power/autosuspend=0\n
|
||||
A: power/autosuspend_delay_ms=0\n
|
||||
A: power/connected_duration=8464967\n
|
||||
A: power/control=auto\n
|
||||
A: power/level=auto\n
|
||||
A: power/runtime_active_time=8464965\n
|
||||
A: power/runtime_status=active\n
|
||||
A: power/runtime_suspended_time=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=1481\n
|
||||
A: version= 2.00\n
|
||||
|
||||
P: /devices/pci0000:00/0000:00:14.0
|
||||
E: DRIVER=xhci_hcd
|
||||
E: ID_AUTOSUSPEND=1
|
||||
E: ID_MODEL_FROM_DATABASE=Sunrise Point-LP USB 3.0 xHCI Controller
|
||||
E: ID_PATH=pci-0000:00:14.0
|
||||
E: ID_PATH_TAG=pci-0000_00_14_0
|
||||
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
|
||||
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
|
||||
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
|
||||
E: ID_VENDOR_FROM_DATABASE=Intel Corporation
|
||||
E: MODALIAS=pci:v00008086d00009D2Fsv000017AAsd00002258bc0Csc03i30
|
||||
E: PCI_CLASS=C0330
|
||||
E: PCI_ID=8086:9D2F
|
||||
E: PCI_SLOT_NAME=0000:00:14.0
|
||||
E: PCI_SUBSYS_ID=17AA:2258
|
||||
E: SUBSYSTEM=pci
|
||||
A: ari_enabled=0\n
|
||||
A: broken_parity_status=0\n
|
||||
A: class=0x0c0330\n
|
||||
H: config=86802F9D060490022130030C00008000040032E8000000000000000000000000000000000000000000000000AA175822000000007000000000000000FF010000FD01348088C60F8000000000000000005B6ECE0F000000000000000000000000306000000000000000000000000000000180C2C10800000000000000000000000500B7001803E0FE0000000000000000090014F01000400100000000C10A080000080000001800008F40020000010400010000000200000000000000000000000000000000000000000000000000000001000000020000000000000000000000000000000000000000000000000000000000000000000000B30F410800000000
|
||||
A: consistent_dma_mask_bits=64\n
|
||||
A: d3cold_allowed=1\n
|
||||
A: dbc=disabled\n
|
||||
A: dbc_bInterfaceProtocol=01\n
|
||||
A: dbc_bcdDevice=0010\n
|
||||
A: dbc_idProduct=0010\n
|
||||
A: dbc_idVendor=1d6b\n
|
||||
A: dbc_poll_interval_ms=64\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:1d
|
||||
L: iommu=../../virtual/iommu/dmar1
|
||||
L: iommu_group=../../../kernel/iommu_groups/4
|
||||
A: irq=128\n
|
||||
A: local_cpulist=0-7\n
|
||||
A: local_cpus=ff\n
|
||||
A: modalias=pci:v00008086d00009D2Fsv000017AAsd00002258bc0Csc03i30\n
|
||||
A: msi_bus=1\n
|
||||
A: msi_irqs/128=msi\n
|
||||
A: msi_irqs/129=msi\n
|
||||
A: msi_irqs/130=msi\n
|
||||
A: msi_irqs/131=msi\n
|
||||
A: msi_irqs/132=msi\n
|
||||
A: msi_irqs/133=msi\n
|
||||
A: msi_irqs/134=msi\n
|
||||
A: msi_irqs/135=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 1 32 128 1\nbuffer-32 0 0 32 0\nxHCI 256 port bw ctx arrays 0 0 256 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 12 13 2112 13\nxHCI ring segments 45 45 4096 45\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 3 32 128 1\nbuffer-32 0 0 32 0\n
|
||||
A: power/control=auto\n
|
||||
A: power/runtime_active_time=8465365\n
|
||||
A: power/runtime_status=active\n
|
||||
A: power/runtime_suspended_time=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=0x00000000e8320000 0x00000000e832ffff 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=0x2258\n
|
||||
A: subsystem_vendor=0x17aa\n
|
||||
A: vendor=0x8086\n
|
||||
|
||||
259
tests/validity/test_tls_hardware.py
Normal file
259
tests/validity/test_tls_hardware.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/python3
|
||||
"""
|
||||
Hardware test for Validity TLS session management (Iteration 2).
|
||||
|
||||
Requires a real Validity/Synaptics sensor (06cb:009a or similar) that has
|
||||
been paired at least once (e.g. via python-validity or Windows driver).
|
||||
|
||||
Run with:
|
||||
sudo LD_LIBRARY_PATH=builddir/libfprint \
|
||||
GI_TYPELIB_PATH=builddir/libfprint \
|
||||
FP_DEVICE_EMULATION=0 \
|
||||
FP_DRIVERS_ALLOWLIST=validity \
|
||||
G_MESSAGES_DEBUG=all \
|
||||
python3 tests/validity/test_tls_hardware.py 2>&1
|
||||
|
||||
The test will:
|
||||
1. Enumerate and detect the validity sensor
|
||||
2. Open the device (triggers: GET_VERSION, CMD19, GET_FW_INFO,
|
||||
flash read, PSK derivation, flash parse, TLS handshake)
|
||||
3. Report whether TLS handshake succeeded or failed
|
||||
4. Close the device cleanly
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import gi
|
||||
gi.require_version('FPrint', '2.0')
|
||||
from gi.repository import FPrint, GLib
|
||||
|
||||
# Exit with error on any exception, including in callbacks
|
||||
sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))
|
||||
|
||||
# Ensure we're not in emulation mode
|
||||
if os.environ.get('FP_DEVICE_EMULATION') == '1':
|
||||
print('ERROR: FP_DEVICE_EMULATION=1 is set, this test needs real hardware')
|
||||
sys.exit(1)
|
||||
|
||||
# Ensure running as root (USB access)
|
||||
if os.geteuid() != 0:
|
||||
print('WARNING: Not running as root — USB access may fail')
|
||||
|
||||
# Collect debug log lines for analysis
|
||||
log_lines = []
|
||||
original_handler = None
|
||||
|
||||
def log_handler(log_domain, log_level, message, user_data):
|
||||
log_lines.append(message)
|
||||
# Also print to stderr for real-time visibility
|
||||
print(f' [{log_domain}] {message}', file=sys.stderr)
|
||||
|
||||
# Install log handler to capture libfprint debug output
|
||||
log_flags = (GLib.LogLevelFlags.LEVEL_DEBUG |
|
||||
GLib.LogLevelFlags.LEVEL_INFO |
|
||||
GLib.LogLevelFlags.LEVEL_MESSAGE |
|
||||
GLib.LogLevelFlags.LEVEL_WARNING |
|
||||
GLib.LogLevelFlags.LEVEL_CRITICAL)
|
||||
|
||||
for domain in ['libfprint', 'libfprint-SSM', 'libfprint-validity',
|
||||
'libfprint-device', 'libfprint-context']:
|
||||
GLib.log_set_handler(domain, log_flags, log_handler, None)
|
||||
|
||||
print('=== Validity TLS Hardware Test ===')
|
||||
print()
|
||||
|
||||
# Step 1: Enumerate devices
|
||||
c = FPrint.Context()
|
||||
c.enumerate()
|
||||
devices = c.get_devices()
|
||||
|
||||
if len(devices) == 0:
|
||||
print('FAIL: No fingerprint devices found')
|
||||
sys.exit(1)
|
||||
|
||||
d = devices[0]
|
||||
del devices
|
||||
|
||||
driver = d.get_driver()
|
||||
print(f'Found device: driver={driver}')
|
||||
|
||||
if driver != 'validity':
|
||||
print(f'SKIP: Expected validity driver, got {driver}')
|
||||
sys.exit(77) # meson skip code
|
||||
|
||||
# Step 2: Open device (this triggers the full TLS flow)
|
||||
print()
|
||||
print('Opening device (GET_VERSION → CMD19 → FW_INFO → Flash Read → PSK → TLS handshake)...')
|
||||
try:
|
||||
d.open_sync()
|
||||
print('Device opened successfully')
|
||||
except GLib.Error as e:
|
||||
print(f'FAIL: open_sync() failed: {e.message}')
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Analyze debug log for TLS progress
|
||||
print()
|
||||
print('=== TLS Progress Analysis ===')
|
||||
|
||||
checks = {
|
||||
'fwext_loaded': False,
|
||||
'fwext_not_loaded': False,
|
||||
'flash_read': False,
|
||||
'flash_bytes': None,
|
||||
'psk_derived': False,
|
||||
'psk_product': None,
|
||||
'flash_cert': False,
|
||||
'flash_privkey': False,
|
||||
'flash_ecdh': False,
|
||||
'keys_loaded': False,
|
||||
'tls_started': False,
|
||||
'server_hello': False,
|
||||
'handshake_done': False,
|
||||
'secure_session': False,
|
||||
'handshake_failed': None,
|
||||
'flash_parse_failed': None,
|
||||
'no_fwext_skip': False,
|
||||
}
|
||||
|
||||
for line in log_lines:
|
||||
if 'Firmware extension is loaded' in line:
|
||||
checks['fwext_loaded'] = True
|
||||
|
||||
if 'Firmware extension not loaded' in line:
|
||||
checks['fwext_not_loaded'] = True
|
||||
|
||||
if 'No firmware extension' in line:
|
||||
checks['no_fwext_skip'] = True
|
||||
if 'TLS flash read: got' in line:
|
||||
checks['flash_read'] = True
|
||||
m = re.search(r'got (\d+) bytes', line)
|
||||
if m:
|
||||
checks['flash_bytes'] = int(m.group(1))
|
||||
|
||||
if 'PSK derived from DMI' in line:
|
||||
checks['psk_derived'] = True
|
||||
m = re.search(r'product=(\S+)', line)
|
||||
if m:
|
||||
checks['psk_product'] = m.group(1)
|
||||
|
||||
if 'TLS flash: certificate loaded' in line:
|
||||
checks['flash_cert'] = True
|
||||
|
||||
if 'TLS flash: private key loaded' in line:
|
||||
checks['flash_privkey'] = True
|
||||
|
||||
if 'TLS flash: ECDH public key loaded' in line:
|
||||
checks['flash_ecdh'] = True
|
||||
|
||||
if 'TLS flash: all keys loaded' in line:
|
||||
checks['keys_loaded'] = True
|
||||
|
||||
if 'TLS ServerHello: cipher 0xC005' in line:
|
||||
checks['server_hello'] = True
|
||||
|
||||
if 'TLS handshake completed' in line:
|
||||
checks['handshake_done'] = True
|
||||
|
||||
if 'TLS session established' in line:
|
||||
checks['secure_session'] = True
|
||||
checks['tls_started'] = True
|
||||
|
||||
if 'TLS handshake failed' in line:
|
||||
checks['handshake_failed'] = line
|
||||
|
||||
if 'TLS flash parse failed' in line:
|
||||
checks['flash_parse_failed'] = line
|
||||
|
||||
if 'skipping TLS' in line.lower() or 'continuing without TLS' in line.lower():
|
||||
pass # noted but not a hard failure for this test
|
||||
|
||||
|
||||
# Report results
|
||||
def report(label, ok, detail=''):
|
||||
status = 'PASS' if ok else 'FAIL'
|
||||
extra = f' ({detail})' if detail else ''
|
||||
print(f' [{status}] {label}{extra}')
|
||||
|
||||
report('Firmware extension loaded',
|
||||
checks['fwext_loaded'],
|
||||
'NOT loaded' if checks['fwext_not_loaded'] else '')
|
||||
|
||||
report('Flash read executed',
|
||||
checks['flash_read'],
|
||||
f"{checks['flash_bytes']} bytes" if checks['flash_bytes'] else '')
|
||||
|
||||
report('PSK derived from DMI',
|
||||
checks['psk_derived'],
|
||||
checks['psk_product'] or '')
|
||||
|
||||
report('Certificate extracted from flash',
|
||||
checks['flash_cert'])
|
||||
|
||||
report('Private key decrypted from flash',
|
||||
checks['flash_privkey'])
|
||||
|
||||
report('ECDH public key extracted from flash',
|
||||
checks['flash_ecdh'])
|
||||
|
||||
report('All TLS keys loaded',
|
||||
checks['keys_loaded'])
|
||||
|
||||
report('ServerHello received (cipher 0xC005)',
|
||||
checks['server_hello'])
|
||||
|
||||
report('TLS handshake completed',
|
||||
checks['handshake_done'])
|
||||
|
||||
report('Secure TLS session established',
|
||||
checks['secure_session'])
|
||||
|
||||
if checks['handshake_failed']:
|
||||
print(f' [INFO] Handshake failure: {checks["handshake_failed"]}')
|
||||
|
||||
if checks['flash_parse_failed']:
|
||||
print(f' [INFO] Flash parse failure: {checks["flash_parse_failed"]}')
|
||||
|
||||
# Step 4: Close device
|
||||
print()
|
||||
print('Closing device...')
|
||||
d.close_sync()
|
||||
print('Device closed successfully')
|
||||
|
||||
del d
|
||||
del c
|
||||
|
||||
# Summary
|
||||
print()
|
||||
all_ok = (checks['fwext_loaded'] and checks['flash_read'] and
|
||||
checks['psk_derived'] and checks['keys_loaded'] and
|
||||
checks['handshake_done'] and checks['secure_session'])
|
||||
fwext_ok_keys_fail = (checks['fwext_loaded'] and checks['flash_read'] and
|
||||
not checks['keys_loaded'])
|
||||
no_fwext = checks['no_fwext_skip'] or checks['fwext_not_loaded']
|
||||
|
||||
if all_ok:
|
||||
print('=== RESULT: ALL TLS CHECKS PASSED ===')
|
||||
print('TLS session established with real hardware.')
|
||||
sys.exit(0)
|
||||
elif no_fwext:
|
||||
print('=== RESULT: FWEXT NOT LOADED ===')
|
||||
print('The firmware extension is not loaded on the sensor.')
|
||||
print('This is required for flash access and TLS handshake.')
|
||||
print()
|
||||
print('To resolve, pair the device first with python-validity:')
|
||||
print(' sudo validity-sensors-firmware # download/upload firmware')
|
||||
print(' sudo python3 -c "from validitysensor.init import open; open()"')
|
||||
print()
|
||||
print('The fwext_loaded check verified the driver correctly detects this.')
|
||||
sys.exit(0) # Not a driver bug — this is expected without fwext
|
||||
elif fwext_ok_keys_fail:
|
||||
print('=== RESULT: PARTIAL — Flash readable but keys incomplete ===')
|
||||
print('Flash read succeeded but TLS key material is missing or corrupt.')
|
||||
print('Re-pairing with python-validity may fix this.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('=== RESULT: SOME TLS CHECKS FAILED ===')
|
||||
sys.exit(1)
|
||||
Loading…
Add table
Reference in a new issue