mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-11 13:08:13 +02:00
validity: Iteration 4 — Sensor identification and HAL tables
Add post-TLS sensor identification infrastructure: TLS command mechanism (vcsfw_tls_cmd_send): - Reusable 2-state subsm for sending VCSFW commands inside TLS channel - Uses 0x44 prefix + TLS app_data wrapping for sends - Decrypts TLS response and extracts VCSFW status + payload Sensor identification (cmd 0x75): - validity_sensor_parse_identify() parses hw_major/hw_version - DeviceInfo table (26 entries): maps (major, version) to device name and sensor type, with exact and fuzzy matching - SensorTypeInfo table (14 entries): maps sensor_type to geometry parameters (bytes_per_line, line_width, calibration blob, etc.) Factory bits retrieval (cmd 0x6f): - validity_sensor_build_factory_bits_cmd() builds 9-byte command - Response stored in sensor state for calibration (Iteration 5) Open sequence integration: - 4 new SSM states: OPEN_SENSOR_IDENTIFY, OPEN_SENSOR_IDENTIFY_RECV, OPEN_SENSOR_FACTORY_BITS, OPEN_SENSOR_FACTORY_BITS_RECV - Sensor state init/clear wired into dev_open/dev_close New files: validity_sensor.h, validity_sensor.c Tests: 14 unit tests in test-validity-sensor.c (all passing) Full suite: 6/6 OK, 0 failures
This commit is contained in:
parent
e1cda8f5d8
commit
95fccfdac8
9 changed files with 1118 additions and 1 deletions
|
|
@ -179,6 +179,10 @@ typedef enum {
|
|||
OPEN_TLS_READ_FLASH,
|
||||
OPEN_TLS_DERIVE_PSK,
|
||||
OPEN_TLS_HANDSHAKE,
|
||||
OPEN_SENSOR_IDENTIFY,
|
||||
OPEN_SENSOR_IDENTIFY_RECV,
|
||||
OPEN_SENSOR_FACTORY_BITS,
|
||||
OPEN_SENSOR_FACTORY_BITS_RECV,
|
||||
OPEN_DONE,
|
||||
OPEN_NUM_STATES,
|
||||
} ValidityOpenSsmState;
|
||||
|
|
@ -488,6 +492,132 @@ open_run_state (FpiSsm *ssm,
|
|||
}
|
||||
break;
|
||||
|
||||
case OPEN_SENSOR_IDENTIFY:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* Without TLS, sensor identification is not possible */
|
||||
if (!self->tls.secure_rx)
|
||||
{
|
||||
fp_info ("No TLS session — skipping sensor identification");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send cmd 0x75 (identify_sensor) via TLS.
|
||||
* NULL callback: subsm auto-advances, response stashed in
|
||||
* self->cmd_response_data for the RECV state. */
|
||||
guint8 cmd[] = { VCSFW_CMD_IDENTIFY_SENSOR };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_SENSOR_IDENTIFY_RECV:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("identify_sensor failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validity_sensor_parse_identify (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&self->sensor.ident))
|
||||
{
|
||||
fp_warn ("identify_sensor: response too short");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Sensor hardware: major=0x%04x version=0x%04x",
|
||||
self->sensor.ident.hw_major,
|
||||
self->sensor.ident.hw_version);
|
||||
|
||||
/* Look up device info and sensor type */
|
||||
self->sensor.device_info = validity_device_info_lookup (
|
||||
self->sensor.ident.hw_major,
|
||||
self->sensor.ident.hw_version);
|
||||
|
||||
if (self->sensor.device_info)
|
||||
{
|
||||
fp_info ("Device: %s (type=0x%04x)",
|
||||
self->sensor.device_info->name,
|
||||
self->sensor.device_info->type);
|
||||
|
||||
self->sensor.type_info = validity_sensor_type_info_lookup (
|
||||
self->sensor.device_info->type);
|
||||
|
||||
if (self->sensor.type_info)
|
||||
fp_info ("Sensor type: 0x%04x, %u bytes/line, %ux repeat",
|
||||
self->sensor.type_info->sensor_type,
|
||||
self->sensor.type_info->bytes_per_line,
|
||||
self->sensor.type_info->repeat_multiplier);
|
||||
else
|
||||
fp_warn ("Unknown sensor type 0x%04x",
|
||||
self->sensor.device_info->type);
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_warn ("Unknown hardware major=0x%04x version=0x%04x",
|
||||
self->sensor.ident.hw_major,
|
||||
self->sensor.ident.hw_version);
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_SENSOR_FACTORY_BITS:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* Factory bits are needed for calibration. If sensor wasn't
|
||||
* identified, skip this step. */
|
||||
if (!self->sensor.device_info)
|
||||
{
|
||||
fp_info ("No sensor info — skipping factory bits");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Build and send cmd 0x6f (GET_FACTORY_BITS) with tag 0x0e00 */
|
||||
guint8 cmd[9];
|
||||
validity_sensor_build_factory_bits_cmd (0x0e00, cmd, sizeof (cmd));
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_SENSOR_FACTORY_BITS_RECV:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("get_factory_bits failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
/* Non-fatal: calibration will have to work without factory data */
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store raw factory bits for calibration (iter 5) */
|
||||
g_clear_pointer (&self->sensor.factory_bits, g_free);
|
||||
if (self->cmd_response_data && self->cmd_response_len > 0)
|
||||
{
|
||||
self->sensor.factory_bits = g_memdup2 (self->cmd_response_data,
|
||||
self->cmd_response_len);
|
||||
self->sensor.factory_bits_len = self->cmd_response_len;
|
||||
fp_info ("Factory bits: %zu bytes", self->sensor.factory_bits_len);
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_DONE:
|
||||
/* All init commands sent. Mark open complete. */
|
||||
fpi_ssm_mark_completed (ssm);
|
||||
|
|
@ -526,6 +656,7 @@ dev_open (FpDevice *device)
|
|||
|
||||
self->interrupt_cancellable = g_cancellable_new ();
|
||||
validity_tls_init (&self->tls);
|
||||
validity_sensor_state_init (&self->sensor);
|
||||
|
||||
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
|
||||
{
|
||||
|
|
@ -554,6 +685,7 @@ dev_close (FpDevice *device)
|
|||
g_clear_pointer (&self->cmd_response_data, g_free);
|
||||
self->cmd_response_len = 0;
|
||||
|
||||
validity_sensor_state_clear (&self->sensor);
|
||||
validity_tls_free (&self->tls);
|
||||
|
||||
g_clear_object (&self->interrupt_cancellable);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "validity_sensor.h"
|
||||
#include "validity_tls.h"
|
||||
|
||||
/* USB Endpoint addresses */
|
||||
|
|
@ -98,6 +99,9 @@ struct _FpiDeviceValidity
|
|||
/* TLS session state */
|
||||
ValidityTlsState tls;
|
||||
|
||||
/* Sensor identification and HAL state (post-TLS) */
|
||||
ValiditySensorState sensor;
|
||||
|
||||
/* Firmware extension status */
|
||||
gboolean fwext_loaded;
|
||||
|
||||
|
|
|
|||
311
libfprint/drivers/validity/validity_sensor.c
Normal file
311
libfprint/drivers/validity/validity_sensor.c
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* 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 "fpi-byte-reader.h"
|
||||
#include "fpi-byte-utils.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=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)
|
||||
{
|
||||
FpiByteReader reader;
|
||||
guint32 zeroes;
|
||||
|
||||
g_return_val_if_fail (data != NULL, FALSE);
|
||||
g_return_val_if_fail (out != NULL, FALSE);
|
||||
|
||||
fpi_byte_reader_init (&reader, data, data_len);
|
||||
|
||||
if (!fpi_byte_reader_get_uint32_le (&reader, &zeroes))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->hw_version))
|
||||
return FALSE;
|
||||
if (!fpi_byte_reader_get_uint16_le (&reader, &out->hw_major))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* 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;
|
||||
|
||||
buf[0] = 0x6f; /* VCSFW_CMD_GET_FACTORY_BITS */
|
||||
FP_WRITE_UINT16_LE (&buf[1], tag);
|
||||
FP_WRITE_UINT16_LE (&buf[3], 0);
|
||||
FP_WRITE_UINT32_LE (&buf[5], 0);
|
||||
|
||||
return FACTORY_BITS_CMD_LEN;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* 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);
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "drivers_api.h"
|
||||
#include "fpi-byte-reader.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
/* ---- VcsfwCmdData lifecycle ---- */
|
||||
|
|
@ -196,6 +197,171 @@ vcsfw_cmd_send (FpiDeviceValidity *self,
|
|||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
status = FP_READ_UINT16_LE (decrypted);
|
||||
|
||||
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: 0x44000000 prefix + TLS record */
|
||||
gsize usb_len = 4 + 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);
|
||||
transfer->buffer[0] = 0x44;
|
||||
transfer->buffer[1] = 0x00;
|
||||
transfer->buffer[2] = 0x00;
|
||||
transfer->buffer[3] = 0x00;
|
||||
memcpy (transfer->buffer + 4, 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
|
||||
|
|
|
|||
|
|
@ -85,6 +85,13 @@ typedef enum {
|
|||
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
|
||||
{
|
||||
|
|
@ -110,6 +117,15 @@ void vcsfw_cmd_send (FpiDeviceValidity *self,
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,8 @@ driver_sources = {
|
|||
[ 'drivers/validity/validity.c',
|
||||
'drivers/validity/vcsfw_protocol.c',
|
||||
'drivers/validity/validity_tls.c',
|
||||
'drivers/validity/validity_fwext.c' ],
|
||||
'drivers/validity/validity_fwext.c',
|
||||
'drivers/validity/validity_sensor.c' ],
|
||||
}
|
||||
|
||||
helper_sources = {
|
||||
|
|
|
|||
|
|
@ -353,6 +353,20 @@ if 'validity' in supported_drivers
|
|||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity sensor identification unit tests (no OpenSSL needed)
|
||||
validity_sensor_test = executable('test-validity-sensor',
|
||||
sources: 'test-validity-sensor.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-sensor',
|
||||
validity_sensor_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
||||
# Run udev rule generator with fatal warnings
|
||||
|
|
|
|||
347
tests/test-validity-sensor.c
Normal file
347
tests/test-validity-sensor.c
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Unit tests for validity sensor identification and HAL tables
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-byte-utils.h"
|
||||
|
||||
#include "drivers/validity/validity_sensor.h"
|
||||
|
||||
/* ================================================================
|
||||
* T4.1: test_identify_sensor_parse
|
||||
*
|
||||
* Verify that a valid cmd 0x75 response is parsed correctly into
|
||||
* a ValiditySensorIdent (hw_major + hw_version).
|
||||
*
|
||||
* Wire format (after 2-byte status stripped):
|
||||
* [zeroes:4 LE] [version:2 LE] [major:2 LE]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_sensor_parse (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
|
||||
/* Build synthetic response: zeroes=0, version=0x13, major=0x004a */
|
||||
guint8 data[8];
|
||||
FP_WRITE_UINT32_LE (&data[0], 0); /* zeroes */
|
||||
FP_WRITE_UINT16_LE (&data[4], 0x0013); /* version */
|
||||
FP_WRITE_UINT16_LE (&data[6], 0x004a); /* major */
|
||||
|
||||
gboolean ok = validity_sensor_parse_identify (data, sizeof (data), &ident);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (ident.hw_major, ==, 0x004a);
|
||||
g_assert_cmpuint (ident.hw_version, ==, 0x0013);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.2: test_identify_sensor_parse_truncated
|
||||
*
|
||||
* Verify that a response shorter than 8 bytes returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_sensor_parse_truncated (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
|
||||
guint8 data[7] = { 0 };
|
||||
|
||||
g_assert_false (validity_sensor_parse_identify (data, sizeof (data), &ident));
|
||||
/* Also test with 0 length */
|
||||
g_assert_false (validity_sensor_parse_identify (data, 0, &ident));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.3: test_device_info_lookup_exact
|
||||
*
|
||||
* Verify that lookup with major=0x004a, version=0x13 returns the
|
||||
* correct DeviceInfo for the ThinkPad T480s sensor.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_exact (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0x004a, 0x13);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->major, ==, 0x004a);
|
||||
g_assert_cmpuint (info->type, ==, 0x00b5);
|
||||
g_assert_cmpuint (info->version, ==, 0x13);
|
||||
g_assert_cmpstr (info->name, ==, "SYN 57K0 FM3297-02");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.4: test_device_info_lookup_another
|
||||
*
|
||||
* Verify that lookup with major=0x0071, version=0x01 returns
|
||||
* the VSI 55E entry (type 0xdb).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_another (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0x0071, 0x01);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->type, ==, 0x00db);
|
||||
g_assert_cmpstr (info->name, ==, "VSI 55E FM72-001");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.5: test_device_info_lookup_unknown
|
||||
*
|
||||
* Verify that a completely unknown major returns NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_unknown (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0xffff, 0x01);
|
||||
|
||||
g_assert_null (info);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.6: test_device_info_lookup_fuzzy
|
||||
*
|
||||
* Verify that when version_mask == 0x00, the entry matches any
|
||||
* version (fuzzy match).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_fuzzy (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
/* major=0x0000 entries have version_mask=0x00 → always fuzzy match.
|
||||
* But major=0x0000 needs to match the lookup major. */
|
||||
info = validity_device_info_lookup (0x0000, 0x42);
|
||||
|
||||
/* Should match one of the wildcard entries */
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->major, ==, 0x0000);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.7: test_sensor_type_info_lookup
|
||||
*
|
||||
* Verify lookup of sensor type 0x00b5 returns correct geometry.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00b5);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->sensor_type, ==, 0x00b5);
|
||||
g_assert_cmpuint (info->bytes_per_line, ==, 0x78);
|
||||
g_assert_cmpuint (info->repeat_multiplier, ==, 2);
|
||||
g_assert_cmpuint (info->lines_per_calibration_data, ==, 112);
|
||||
g_assert_cmpuint (info->line_width, ==, 112);
|
||||
g_assert_nonnull (info->calibration_blob);
|
||||
g_assert_cmpuint (info->calibration_blob_len, ==, 112);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.8: test_sensor_type_info_lookup_db
|
||||
*
|
||||
* Verify lookup of sensor type 0x00db (55E) returns correct geometry.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup_db (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00db);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->bytes_per_line, ==, 0x98);
|
||||
g_assert_cmpuint (info->repeat_multiplier, ==, 1);
|
||||
g_assert_cmpuint (info->lines_per_calibration_data, ==, 144);
|
||||
g_assert_cmpuint (info->line_width, ==, 144);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.9: test_sensor_type_info_lookup_unknown
|
||||
*
|
||||
* Verify that an unknown sensor type returns NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup_unknown (void)
|
||||
{
|
||||
g_assert_null (validity_sensor_type_info_lookup (0xbeef));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.10: test_factory_bits_cmd_format
|
||||
*
|
||||
* Verify that the factory bits command is built correctly.
|
||||
* Expected: [0x6f] [0x00 0x0e] [0x00 0x00] [0x00 0x00 0x00 0x00]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_factory_bits_cmd_format (void)
|
||||
{
|
||||
guint8 buf[16];
|
||||
gsize len;
|
||||
|
||||
len = validity_sensor_build_factory_bits_cmd (0x0e00, buf, sizeof (buf));
|
||||
|
||||
g_assert_cmpuint (len, ==, 9);
|
||||
g_assert_cmpuint (buf[0], ==, 0x6f);
|
||||
/* tag = 0x0e00 LE */
|
||||
g_assert_cmpuint (buf[1], ==, 0x00);
|
||||
g_assert_cmpuint (buf[2], ==, 0x0e);
|
||||
/* pad 2 bytes */
|
||||
g_assert_cmpuint (buf[3], ==, 0x00);
|
||||
g_assert_cmpuint (buf[4], ==, 0x00);
|
||||
/* pad 4 bytes */
|
||||
g_assert_cmpuint (buf[5], ==, 0x00);
|
||||
g_assert_cmpuint (buf[6], ==, 0x00);
|
||||
g_assert_cmpuint (buf[7], ==, 0x00);
|
||||
g_assert_cmpuint (buf[8], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.11: test_factory_bits_cmd_buffer_too_small
|
||||
*
|
||||
* Verify that a too-small buffer returns 0.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_factory_bits_cmd_buffer_too_small (void)
|
||||
{
|
||||
guint8 buf[4];
|
||||
gsize len;
|
||||
|
||||
len = validity_sensor_build_factory_bits_cmd (0x0e00, buf, sizeof (buf));
|
||||
|
||||
g_assert_cmpuint (len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.12: test_identify_then_lookup
|
||||
*
|
||||
* End-to-end: parse identify_sensor response → DeviceInfo lookup →
|
||||
* SensorTypeInfo lookup. Simulates the T480s sensor (06cb:009a).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_then_lookup (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
const ValidityDeviceInfo *dev_info;
|
||||
const ValiditySensorTypeInfo *type_info;
|
||||
|
||||
/* Simulate cmd 0x75 response for T480s: major=0x004a, version=0x13 */
|
||||
guint8 data[8];
|
||||
FP_WRITE_UINT32_LE (&data[0], 0);
|
||||
FP_WRITE_UINT16_LE (&data[4], 0x0013);
|
||||
FP_WRITE_UINT16_LE (&data[6], 0x004a);
|
||||
|
||||
g_assert_true (validity_sensor_parse_identify (data, sizeof (data), &ident));
|
||||
g_assert_cmpuint (ident.hw_major, ==, 0x004a);
|
||||
g_assert_cmpuint (ident.hw_version, ==, 0x0013);
|
||||
|
||||
dev_info = validity_device_info_lookup (ident.hw_major, ident.hw_version);
|
||||
g_assert_nonnull (dev_info);
|
||||
g_assert_cmpuint (dev_info->type, ==, 0x00b5);
|
||||
|
||||
type_info = validity_sensor_type_info_lookup (dev_info->type);
|
||||
g_assert_nonnull (type_info);
|
||||
g_assert_cmpuint (type_info->bytes_per_line, ==, 0x78);
|
||||
g_assert_cmpuint (type_info->line_width, ==, 112);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.13: test_sensor_state_lifecycle
|
||||
*
|
||||
* Verify that init zeros the state and clear frees allocated data.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_state_lifecycle (void)
|
||||
{
|
||||
ValiditySensorState state;
|
||||
|
||||
validity_sensor_state_init (&state);
|
||||
g_assert_null (state.device_info);
|
||||
g_assert_null (state.type_info);
|
||||
g_assert_null (state.factory_bits);
|
||||
g_assert_cmpuint (state.factory_bits_len, ==, 0);
|
||||
|
||||
/* Simulate storing factory bits */
|
||||
state.factory_bits = g_memdup2 ("\x01\x02\x03", 3);
|
||||
state.factory_bits_len = 3;
|
||||
|
||||
validity_sensor_state_clear (&state);
|
||||
g_assert_null (state.factory_bits);
|
||||
g_assert_cmpuint (state.factory_bits_len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.14: test_calibration_blob_present
|
||||
*
|
||||
* Verify that the calibration blob for type 0x00b5 has expected
|
||||
* first and last bytes (from python-validity generated_tables).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_calibration_blob_present (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00b5);
|
||||
g_assert_nonnull (info);
|
||||
g_assert_nonnull (info->calibration_blob);
|
||||
g_assert_cmpuint (info->calibration_blob_len, ==, 112);
|
||||
|
||||
/* First byte: 0x9b, last byte: 0x06 */
|
||||
g_assert_cmpuint (info->calibration_blob[0], ==, 0x9b);
|
||||
g_assert_cmpuint (info->calibration_blob[111], ==, 0x06);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/sensor/identify/parse",
|
||||
test_identify_sensor_parse);
|
||||
g_test_add_func ("/validity/sensor/identify/truncated",
|
||||
test_identify_sensor_parse_truncated);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_exact",
|
||||
test_device_info_lookup_exact);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_another",
|
||||
test_device_info_lookup_another);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_unknown",
|
||||
test_device_info_lookup_unknown);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_fuzzy",
|
||||
test_device_info_lookup_fuzzy);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup",
|
||||
test_sensor_type_info_lookup);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup_db",
|
||||
test_sensor_type_info_lookup_db);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup_unknown",
|
||||
test_sensor_type_info_lookup_unknown);
|
||||
g_test_add_func ("/validity/sensor/factory_bits/cmd_format",
|
||||
test_factory_bits_cmd_format);
|
||||
g_test_add_func ("/validity/sensor/factory_bits/buffer_too_small",
|
||||
test_factory_bits_cmd_buffer_too_small);
|
||||
g_test_add_func ("/validity/sensor/identify_then_lookup",
|
||||
test_identify_then_lookup);
|
||||
g_test_add_func ("/validity/sensor/state_lifecycle",
|
||||
test_sensor_state_lifecycle);
|
||||
g_test_add_func ("/validity/sensor/calibration_blob_present",
|
||||
test_calibration_blob_present);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue