From 65277fce2f290133160707900dbaf8ddaf586ee9 Mon Sep 17 00:00:00 2001 From: Leonardo Francisco Date: Thu, 9 Apr 2026 20:36:45 -0400 Subject: [PATCH] Refactor Validity Driver Code for Improved Readability and Maintainability - Introduced helper functions in validity_tls.c to encapsulate command sending and response handling for TLS flash read and handshake processes, reducing code duplication and enhancing clarity. - Reorganized the validity_verify.c file by creating dedicated functions for each step in the verification process, including capture building, matching, and result retrieval, to streamline the state machine logic. - Enhanced the delete and clear operations in validity_verify.c by modularizing the code into smaller functions, improving readability and maintainability. - Updated the list operations in validity_verify.c to utilize helper functions for fetching user storage and user details, simplifying the state management. - Overall, these changes aim to improve the structure of the code, making it easier to follow and modify in the future. --- libfprint/drivers/validity/validity.c | 1276 ++++++++------ libfprint/drivers/validity/validity_enroll.c | 1659 +++++++++++------- libfprint/drivers/validity/validity_fwext.c | 575 +++--- libfprint/drivers/validity/validity_pair.c | 1346 ++++++++------ libfprint/drivers/validity/validity_tls.c | 241 ++- libfprint/drivers/validity/validity_verify.c | 850 +++++---- 6 files changed, 3446 insertions(+), 2501 deletions(-) diff --git a/libfprint/drivers/validity/validity.c b/libfprint/drivers/validity/validity.c index fc549c81..b1ecb19c 100644 --- a/libfprint/drivers/validity/validity.c +++ b/libfprint/drivers/validity/validity.c @@ -383,651 +383,797 @@ calib_bulk_read_cb (FpiUsbTransfer *transfer, fpi_ssm_next_state (transfer->ssm); } +static void +open_get_version (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + VALIDITY_USB_SEND_HEADER_LEN); + transfer->buffer[0] = VCSFW_CMD_GET_VERSION; + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_recv_version (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + 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, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_send_cmd19 (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + VALIDITY_USB_SEND_HEADER_LEN); + transfer->buffer[0] = VCSFW_CMD_UNKNOWN_INIT; + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_recv_cmd19 (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + 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, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_send_get_fw_info (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, 0x02 }; + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + sizeof (cmd)); + memcpy (transfer->buffer, cmd, sizeof (cmd)); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_recv_get_fw_info (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + 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, + fw_info_recv_cb, NULL); +} + +static void +open_send_init_hardcoded (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + const ValidityDeviceDesc *desc = + validity_hal_device_lookup (self->dev_type); + + if (!desc || !desc->init_hardcoded) + { + fp_warn ("No init_hardcoded blob for dev_type %u — skipping", + self->dev_type); + fpi_ssm_jump_to_state (ssm, OPEN_UPLOAD_FWEXT); + return; + } + + /* In emulation mode, skip raw USB blobs */ + if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) + { + fp_dbg ("Emulation mode — skipping init_hardcoded"); + fpi_ssm_jump_to_state (ssm, OPEN_UPLOAD_FWEXT); + return; + } + + fp_dbg ("Sending init_hardcoded (%zu bytes)", desc->init_hardcoded_len); + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + desc->init_hardcoded_len); + memcpy (transfer->buffer, desc->init_hardcoded, + desc->init_hardcoded_len); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_recv_init_hardcoded (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + 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, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_send_init_clean_slate (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + /* clean_slate is only sent when fwext is NOT loaded */ + if (self->fwext_loaded) + { + fpi_ssm_next_state (ssm); + return; + } + + const ValidityDeviceDesc *desc = + validity_hal_device_lookup (self->dev_type); + + if (!desc || !desc->init_clean_slate) + { + fp_dbg ("No init_clean_slate blob — skipping"); + fpi_ssm_next_state (ssm); + return; + } + + fp_info ("Fwext not loaded — sending init_clean_slate (%zu bytes)", + desc->init_clean_slate_len); + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, + desc->init_clean_slate_len); + memcpy (transfer->buffer, desc->init_clean_slate, + desc->init_clean_slate_len); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_recv_init_clean_slate (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + /* If fwext loaded, we skipped the send — just advance */ + if (self->fwext_loaded) + { + fpi_ssm_next_state (ssm); + return; + } + + 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, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +open_upload_fwext (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + + /* If fwext is already loaded, skip upload */ + if (self->fwext_loaded) + { + fpi_ssm_next_state (ssm); + return; + } + + /* In emulation mode, skip upload — no real device */ + if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) + { + fp_dbg ("Emulation mode — skipping fwext upload"); + fpi_ssm_next_state (ssm); + return; + } + + /* Fwext upload requires a TLS session (flash writes need TLS). + * If TLS handshake failed/skipped, we can't upload. */ + if (!self->tls.secure_rx) + { + fp_warn ("No TLS session — cannot upload firmware extension " + "(device may need pairing first)"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + fp_info ("Firmware extension not loaded — starting upload via TLS"); + + self->open_ssm = ssm; + FpiSsm *fwext_ssm = validity_fwext_upload_ssm_new (dev); + fpi_ssm_start (fwext_ssm, fwext_upload_ssm_done); +} + +static void +open_pair (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + + /* In emulation mode, skip pairing */ + if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) + { + fp_dbg ("Emulation mode — skipping pairing check"); + fpi_ssm_next_state (ssm); + return; + } + + fp_info ("Starting pairing check…"); + validity_pair_state_init (&self->pair_state); + self->open_ssm = ssm; + FpiSsm *pair_ssm = validity_pair_ssm_new (dev); + fpi_ssm_start (pair_ssm, pair_ssm_done); +} + +static void +open_tls_read_flash (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* In emulation mode (tests), skip TLS — no real device to talk to */ + if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) + { + fp_dbg ("Emulation mode — skipping TLS flash read"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + /* Read flash partition 1 to get TLS keys. + * TLS works independently of fwext (partition 2). */ + self->open_ssm = ssm; + FpiSsm *flash_ssm = fpi_ssm_new (dev, + validity_tls_flash_read_run_state, + TLS_FLASH_READ_NUM_STATES); + fpi_ssm_start (flash_ssm, flash_read_ssm_done); +} + +static void +open_tls_derive_psk (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Derive PSK from hardware identity (DMI) */ + validity_tls_derive_psk (&self->tls); + + /* Flash response format (after 2-byte status already stripped): + * [size:4 LE][unknown:2][flash_data:size] + * See python-validity: sz, = unpack('cmd_response_data && self->cmd_response_len > 6) + { + guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data); + const guint8 *flash_data = self->cmd_response_data + 6; + gsize flash_avail = self->cmd_response_len - 6; + + if (flash_sz > flash_avail) + flash_sz = flash_avail; + + fp_dbg ("TLS flash: %u bytes of data (response had %zu)", + flash_sz, self->cmd_response_len); + + if (!validity_tls_parse_flash (&self->tls, + flash_data, + flash_sz, + &error)) + { + fp_warn ("TLS flash parse failed: %s — " + "device may need pairing", error->message); + /* Non-fatal for now: skip TLS handshake */ + g_clear_error (&error); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + } + else + { + fp_warn ("No flash data available — skipping TLS"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + fpi_ssm_next_state (ssm); +} + +static void +open_tls_handshake (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + if (!self->tls.keys_loaded) + { + fp_info ("TLS keys not loaded — skipping handshake"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + self->open_ssm = ssm; + FpiSsm *tls_ssm = fpi_ssm_new (dev, + validity_tls_handshake_run_state, + TLS_HS_NUM_STATES); + fpi_ssm_start (tls_ssm, tls_handshake_ssm_done); +} + +static void +open_sensor_identify (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* 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); +} + +static void +open_sensor_identify_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + 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; + } + + { + GString *hex = g_string_new ("identify_sensor raw: "); + for (gsize i = 0; i < self->cmd_response_len; i++) + g_string_append_printf (hex, "%02x ", self->cmd_response_data[i]); + fp_dbg ("%s", hex->str); + g_string_free (hex, TRUE); + } + + 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); +} + +static void +open_sensor_factory_bits (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* 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); +} + +static void +open_sensor_factory_bits_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + 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); +} + +static void +open_capture_setup (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Initialize capture state from sensor identification and factory bits. + * Requires: sensor.type_info and sensor.device_info from IDENTIFY, + * sensor.factory_bits from FACTORY_BITS. */ + if (!self->sensor.type_info || !self->sensor.device_info) + { + fp_info ("No sensor type info — skipping capture setup"); + fpi_ssm_next_state (ssm); + return; + } + + validity_capture_state_init (&self->capture); + + if (!validity_capture_state_setup (&self->capture, + self->sensor.type_info, + self->sensor.device_info->type, + self->version_info.version_major, + self->version_info.version_minor, + self->sensor.factory_bits, + self->sensor.factory_bits_len)) + { + fp_warn ("Capture state setup failed — " + "enrollment/verification will not be available"); + /* Non-fatal: device can still be used for identification + * if calibration data exists on flash */ + } + else + { + fp_info ("Capture state: %u bytes/line, %u lines/frame, " + "type1=%d", + self->capture.bytes_per_line, + self->capture.lines_per_frame, + self->capture.is_type1_device); + } + + fpi_ssm_next_state (ssm); +} + +static void +open_calibrate_build (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Run calibration captures to establish sensor finger-detect baseline. + * PY: sensor.calibrate() — 3 iterations of CALIBRATE capture. + * Without this, chunk 0x26 (Finger Detect) always triggers. */ + if (!self->sensor.type_info || + self->capture.bytes_per_line == 0) + { + fp_info ("No capture state — skipping calibration"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + self->calib_iteration = 0; + g_clear_pointer (&self->capture.calib_data, g_free); + self->capture.calib_data_len = 0; + + fp_info ("Starting sensor calibration (%u iterations)", + self->capture.calibration_iterations); + fpi_ssm_next_state (ssm); +} + +static void +open_calibrate_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd; + + cmd = validity_capture_build_cmd_02 (&self->capture, + self->sensor.type_info, + VALIDITY_CAPTURE_CALIBRATE, + &cmd_len); + if (!cmd) + { + fp_warn ("Failed to build calibration capture command"); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + fp_dbg ("Calibration iteration %u/%u", + self->calib_iteration + 1, + self->capture.calibration_iterations); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +open_calibrate_send_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("Calibration capture failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_jump_to_state (ssm, OPEN_DONE); + return; + } + + /* Read calibration data from EP 0x82. + * PY: usb.read_82() — reads all bulk data from the sensor. */ + { + gsize expected_size = (gsize) (self->capture.calibration_frames * + self->capture.lines_per_frame + 1) * + self->capture.bytes_per_line; + FpiUsbTransfer *xfer = fpi_usb_transfer_new (dev); + + fp_dbg ("Reading calibration data: %zu bytes from EP 0x82", + expected_size); + xfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (xfer, VALIDITY_EP_DATA_IN, + expected_size); + fpi_usb_transfer_submit (xfer, 5000, NULL, + calib_bulk_read_cb, NULL); + } +} + +static void +open_calibrate_read_data (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->bulk_data && self->bulk_data_len > 0) + { + /* Average the raw calibration frames */ + gsize averaged_len = 0; + guint8 *averaged = validity_capture_average_frames ( + self->bulk_data, + self->bulk_data_len, + self->capture.lines_per_frame, + self->capture.bytes_per_line, + self->sensor.type_info->lines_per_calibration_data, + self->capture.calibration_frames, + &averaged_len); + + if (averaged && averaged_len > 0) + { + /* Process calibration: scale and accumulate into calib_data */ + validity_capture_process_calibration ( + &self->capture.calib_data, + &self->capture.calib_data_len, + averaged, + averaged_len, + self->capture.bytes_per_line); + + fp_dbg ("Calibration iteration %u complete: " + "averaged %zu bytes, calib_data %zu bytes", + self->calib_iteration + 1, + averaged_len, + self->capture.calib_data_len); + g_free (averaged); + } + else + { + fp_dbg ("Calibration iteration %u: averaging failed", + self->calib_iteration + 1); + g_free (averaged); + } + + g_clear_pointer (&self->bulk_data, g_free); + self->bulk_data_len = 0; + } + else + { + fp_dbg ("Calibration iteration %u: no bulk data", + self->calib_iteration + 1); + } + + fpi_ssm_next_state (ssm); +} + +static void +open_calibrate_loop (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + self->calib_iteration++; + if (self->calib_iteration < self->capture.calibration_iterations) + { + fpi_ssm_jump_to_state (ssm, OPEN_CALIBRATE_SEND); + } + else + { + fp_info ("Sensor calibration complete"); + fpi_ssm_next_state (ssm); + } +} + static void open_run_state (FpiSsm *ssm, FpDevice *dev) { - FpiUsbTransfer *transfer; + FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); switch (fpi_ssm_get_cur_state (ssm)) { case OPEN_GET_VERSION: - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, - VALIDITY_USB_SEND_HEADER_LEN); - transfer->buffer[0] = VCSFW_CMD_GET_VERSION; - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); + open_get_version (ssm, self); break; case OPEN_RECV_VERSION: - 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, - fpi_ssm_usb_transfer_cb, NULL); + open_recv_version (ssm, self); break; case OPEN_SEND_CMD19: - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, - VALIDITY_USB_SEND_HEADER_LEN); - transfer->buffer[0] = VCSFW_CMD_UNKNOWN_INIT; - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); + open_send_cmd19 (ssm, self); break; case OPEN_RECV_CMD19: - 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, - fpi_ssm_usb_transfer_cb, NULL); + open_recv_cmd19 (ssm, self); break; case OPEN_SEND_GET_FW_INFO: - { - guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, 0x02 }; - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, - sizeof (cmd)); - memcpy (transfer->buffer, cmd, sizeof (cmd)); - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); - } + open_send_get_fw_info (ssm, self); break; case OPEN_RECV_GET_FW_INFO: - 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, - fw_info_recv_cb, NULL); + open_recv_get_fw_info (ssm, self); break; case OPEN_SEND_INIT_HARDCODED: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - const ValidityDeviceDesc *desc = - validity_hal_device_lookup (self->dev_type); - - if (!desc || !desc->init_hardcoded) - { - fp_warn ("No init_hardcoded blob for dev_type %u — skipping", - self->dev_type); - fpi_ssm_jump_to_state (ssm, OPEN_UPLOAD_FWEXT); - return; - } - - /* In emulation mode, skip raw USB blobs */ - if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) - { - fp_dbg ("Emulation mode — skipping init_hardcoded"); - fpi_ssm_jump_to_state (ssm, OPEN_UPLOAD_FWEXT); - return; - } - - fp_dbg ("Sending init_hardcoded (%zu bytes)", desc->init_hardcoded_len); - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, - desc->init_hardcoded_len); - memcpy (transfer->buffer, desc->init_hardcoded, - desc->init_hardcoded_len); - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); - } + open_send_init_hardcoded (ssm, self); break; case OPEN_RECV_INIT_HARDCODED: - 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, - fpi_ssm_usb_transfer_cb, NULL); + open_recv_init_hardcoded (ssm, self); break; case OPEN_SEND_INIT_CLEAN_SLATE: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* clean_slate is only sent when fwext is NOT loaded */ - if (self->fwext_loaded) - { - fpi_ssm_next_state (ssm); - return; - } - - const ValidityDeviceDesc *desc = - validity_hal_device_lookup (self->dev_type); - - if (!desc || !desc->init_clean_slate) - { - fp_dbg ("No init_clean_slate blob — skipping"); - fpi_ssm_next_state (ssm); - return; - } - - fp_info ("Fwext not loaded — sending init_clean_slate (%zu bytes)", - desc->init_clean_slate_len); - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, - desc->init_clean_slate_len); - memcpy (transfer->buffer, desc->init_clean_slate, - desc->init_clean_slate_len); - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); - } + open_send_init_clean_slate (ssm, self); break; case OPEN_RECV_INIT_CLEAN_SLATE: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* If fwext loaded, we skipped the send — just advance */ - if (self->fwext_loaded) - { - fpi_ssm_next_state (ssm); - return; - } - - 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, - fpi_ssm_usb_transfer_cb, NULL); - } + open_recv_init_clean_slate (ssm, self); break; case OPEN_UPLOAD_FWEXT: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* If fwext is already loaded, skip upload */ - if (self->fwext_loaded) - { - fpi_ssm_next_state (ssm); - return; - } - - /* In emulation mode, skip upload — no real device */ - if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) - { - fp_dbg ("Emulation mode — skipping fwext upload"); - fpi_ssm_next_state (ssm); - return; - } - - /* Fwext upload requires a TLS session (flash writes need TLS). - * If TLS handshake failed/skipped, we can't upload. */ - if (!self->tls.secure_rx) - { - fp_warn ("No TLS session — cannot upload firmware extension " - "(device may need pairing first)"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - fp_info ("Firmware extension not loaded — starting upload via TLS"); - - self->open_ssm = ssm; - FpiSsm *fwext_ssm = validity_fwext_upload_ssm_new (dev); - fpi_ssm_start (fwext_ssm, fwext_upload_ssm_done); - } + open_upload_fwext (ssm, self); break; case OPEN_PAIR: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* In emulation mode, skip pairing */ - if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) - { - fp_dbg ("Emulation mode — skipping pairing check"); - fpi_ssm_next_state (ssm); - return; - } - - fp_info ("Starting pairing check…"); - validity_pair_state_init (&self->pair_state); - self->open_ssm = ssm; - FpiSsm *pair_ssm = validity_pair_ssm_new (dev); - fpi_ssm_start (pair_ssm, pair_ssm_done); - } + open_pair (ssm, self); break; case OPEN_TLS_READ_FLASH: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* In emulation mode (tests), skip TLS — no real device to talk to */ - if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0) - { - fp_dbg ("Emulation mode — skipping TLS flash read"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - /* Read flash partition 1 to get TLS keys. - * TLS works independently of fwext (partition 2). */ - self->open_ssm = ssm; - FpiSsm *flash_ssm = fpi_ssm_new (dev, - validity_tls_flash_read_run_state, - TLS_FLASH_READ_NUM_STATES); - fpi_ssm_start (flash_ssm, flash_read_ssm_done); - } + open_tls_read_flash (ssm, self); break; case OPEN_TLS_DERIVE_PSK: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* Derive PSK from hardware identity (DMI) */ - validity_tls_derive_psk (&self->tls); - - /* Flash response format (after 2-byte status already stripped): - * [size:4 LE][unknown:2][flash_data:size] - * See python-validity: sz, = unpack('cmd_response_data && self->cmd_response_len > 6) - { - guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data); - const guint8 *flash_data = self->cmd_response_data + 6; - gsize flash_avail = self->cmd_response_len - 6; - - if (flash_sz > flash_avail) - flash_sz = flash_avail; - - fp_dbg ("TLS flash: %u bytes of data (response had %zu)", - flash_sz, self->cmd_response_len); - - if (!validity_tls_parse_flash (&self->tls, - flash_data, - flash_sz, - &error)) - { - fp_warn ("TLS flash parse failed: %s — " - "device may need pairing", error->message); - /* Non-fatal for now: skip TLS handshake */ - g_clear_error (&error); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - } - else - { - fp_warn ("No flash data available — skipping TLS"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - fpi_ssm_next_state (ssm); - } + open_tls_derive_psk (ssm, self); break; case OPEN_TLS_HANDSHAKE: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - if (!self->tls.keys_loaded) - { - fp_info ("TLS keys not loaded — skipping handshake"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - self->open_ssm = ssm; - FpiSsm *tls_ssm = fpi_ssm_new (dev, - validity_tls_handshake_run_state, - TLS_HS_NUM_STATES); - fpi_ssm_start (tls_ssm, tls_handshake_ssm_done); - } + open_tls_handshake (ssm, self); 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); - } + open_sensor_identify (ssm, self); 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; - } - - { - GString *hex = g_string_new ("identify_sensor raw: "); - for (gsize i = 0; i < self->cmd_response_len; i++) - g_string_append_printf (hex, "%02x ", self->cmd_response_data[i]); - fp_dbg ("%s", hex->str); - g_string_free (hex, TRUE); - } - - 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); - } + open_sensor_identify_recv (ssm, self); 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); - } + open_sensor_factory_bits (ssm, self); 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); - } + open_sensor_factory_bits_recv (ssm, self); break; case OPEN_CAPTURE_SETUP: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* Initialize capture state from sensor identification and factory bits. - * Requires: sensor.type_info and sensor.device_info from IDENTIFY, - * sensor.factory_bits from FACTORY_BITS. */ - if (!self->sensor.type_info || !self->sensor.device_info) - { - fp_info ("No sensor type info — skipping capture setup"); - fpi_ssm_next_state (ssm); - return; - } - - validity_capture_state_init (&self->capture); - - if (!validity_capture_state_setup (&self->capture, - self->sensor.type_info, - self->sensor.device_info->type, - self->version_info.version_major, - self->version_info.version_minor, - self->sensor.factory_bits, - self->sensor.factory_bits_len)) - { - fp_warn ("Capture state setup failed — " - "enrollment/verification will not be available"); - /* Non-fatal: device can still be used for identification - * if calibration data exists on flash */ - } - else - { - fp_info ("Capture state: %u bytes/line, %u lines/frame, " - "type1=%d", - self->capture.bytes_per_line, - self->capture.lines_per_frame, - self->capture.is_type1_device); - } - - fpi_ssm_next_state (ssm); - } + open_capture_setup (ssm, self); break; case OPEN_CALIBRATE_BUILD: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - /* Run calibration captures to establish sensor finger-detect baseline. - * PY: sensor.calibrate() — 3 iterations of CALIBRATE capture. - * Without this, chunk 0x26 (Finger Detect) always triggers. */ - if (!self->sensor.type_info || - self->capture.bytes_per_line == 0) - { - fp_info ("No capture state — skipping calibration"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - self->calib_iteration = 0; - g_clear_pointer (&self->capture.calib_data, g_free); - self->capture.calib_data_len = 0; - - fp_info ("Starting sensor calibration (%u iterations)", - self->capture.calibration_iterations); - fpi_ssm_next_state (ssm); - } + open_calibrate_build (ssm, self); break; case OPEN_CALIBRATE_SEND: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - gsize cmd_len; - guint8 *cmd; - - cmd = validity_capture_build_cmd_02 (&self->capture, - self->sensor.type_info, - VALIDITY_CAPTURE_CALIBRATE, - &cmd_len); - if (!cmd) - { - fp_warn ("Failed to build calibration capture command"); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - fp_dbg ("Calibration iteration %u/%u", - self->calib_iteration + 1, - self->capture.calibration_iterations); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + open_calibrate_send (ssm, self); break; case OPEN_CALIBRATE_SEND_RECV: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("Calibration capture failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_jump_to_state (ssm, OPEN_DONE); - return; - } - - /* Read calibration data from EP 0x82. - * PY: usb.read_82() — reads all bulk data from the sensor. */ - { - gsize expected_size = (gsize) (self->capture.calibration_frames * - self->capture.lines_per_frame + 1) * - self->capture.bytes_per_line; - FpiUsbTransfer *xfer = fpi_usb_transfer_new (dev); - - fp_dbg ("Reading calibration data: %zu bytes from EP 0x82", - expected_size); - xfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (xfer, VALIDITY_EP_DATA_IN, - expected_size); - fpi_usb_transfer_submit (xfer, 5000, NULL, - calib_bulk_read_cb, NULL); - } - } + open_calibrate_send_recv (ssm, self); break; case OPEN_CALIBRATE_READ_DATA: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - if (self->bulk_data && self->bulk_data_len > 0) - { - /* Average the raw calibration frames */ - gsize averaged_len = 0; - guint8 *averaged = validity_capture_average_frames ( - self->bulk_data, - self->bulk_data_len, - self->capture.lines_per_frame, - self->capture.bytes_per_line, - self->sensor.type_info->lines_per_calibration_data, - self->capture.calibration_frames, - &averaged_len); - - if (averaged && averaged_len > 0) - { - /* Process calibration: scale and accumulate into calib_data */ - validity_capture_process_calibration ( - &self->capture.calib_data, - &self->capture.calib_data_len, - averaged, - averaged_len, - self->capture.bytes_per_line); - - fp_dbg ("Calibration iteration %u complete: " - "averaged %zu bytes, calib_data %zu bytes", - self->calib_iteration + 1, - averaged_len, - self->capture.calib_data_len); - g_free (averaged); - } - else - { - fp_dbg ("Calibration iteration %u: averaging failed", - self->calib_iteration + 1); - g_free (averaged); - } - - g_clear_pointer (&self->bulk_data, g_free); - self->bulk_data_len = 0; - } - else - { - fp_dbg ("Calibration iteration %u: no bulk data", - self->calib_iteration + 1); - } - - fpi_ssm_next_state (ssm); - } + open_calibrate_read_data (ssm, self); break; case OPEN_CALIBRATE_LOOP: - { - FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - - self->calib_iteration++; - if (self->calib_iteration < self->capture.calibration_iterations) - { - fpi_ssm_jump_to_state (ssm, OPEN_CALIBRATE_SEND); - } - else - { - fp_info ("Sensor calibration complete"); - fpi_ssm_next_state (ssm); - } - } + open_calibrate_loop (ssm, self); break; case OPEN_DONE: diff --git a/libfprint/drivers/validity/validity_enroll.c b/libfprint/drivers/validity/validity_enroll.c index c89fff5d..e291e014 100644 --- a/libfprint/drivers/validity/validity_enroll.c +++ b/libfprint/drivers/validity/validity_enroll.c @@ -292,6 +292,933 @@ parse_enrollment_update_response (const guint8 *data, * Enrollment SSM * ================================================================ */ + +static void +enroll_cleanup_stale (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Close any stale enrollment session before starting fresh. + * Firmware may have leftover state from a previous session + * (e.g. if enrollment was interrupted). Send cmd 0x69 flag=0 + * (enrollment_update_end) — errors are expected and ignored. */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_create_enrollment (FALSE, &cmd_len); + + self->enroll_key = 0; + self->enroll_stage = 0; + self->scan_incomplete_count = 0; + g_clear_pointer (&self->enroll_template, g_free); + self->enroll_template_len = 0; + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_pre_get_storage (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Check for existing user records that would cause 0x0526 */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user_storage ( + VALIDITY_STORAGE_NAME, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_pre_get_storage_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + validity_user_storage_clear (&self->list_storage); + + if (self->cmd_response_status != VCSFW_STATUS_OK || + !self->cmd_response_data || + !validity_db_parse_user_storage (self->cmd_response_data, + self->cmd_response_len, + &self->list_storage)) + { + /* No storage or parse error — skip cleanup, go to enrollment */ + fpi_ssm_jump_to_state (ssm, ENROLL_START); + return; + } + + if (self->list_storage.user_count == 0) + { + fp_dbg ("No existing users — skipping pre-enrollment cleanup"); + fpi_ssm_jump_to_state (ssm, ENROLL_START); + return; + } + + fp_info ("Pre-enrollment cleanup: deleting %u existing user(s)", + self->list_storage.user_count); + self->list_user_idx = 0; + fpi_ssm_next_state (ssm); +} + +static void +enroll_pre_del_user (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->list_user_idx >= self->list_storage.user_count) + { + fp_info ("Pre-enrollment cleanup done"); + validity_user_storage_clear (&self->list_storage); + fpi_ssm_jump_to_state (ssm, ENROLL_START); + return; + } + + guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len); + fp_info ("Deleting user record dbid=%u", user_dbid); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_pre_del_user_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("Pre-enrollment del_record(dbid=%u) failed: status=0x%04x", + self->list_storage.user_dbids[self->list_user_idx], + self->cmd_response_status); + + self->list_user_idx++; + fpi_ssm_jump_to_state (ssm, ENROLL_PRE_DEL_USER); +} + +static void +enroll_start (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x69 flag=1: create enrollment session */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_create_enrollment (TRUE, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_start_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("create_enrollment failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +enroll_led_on_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* LED is on — signal that we need a finger. + * PY sends capture IMMEDIATELY after glow_start_scan(), no delay. + * The ENROLL finger detect (chunk 0x26) needs to see the transition + * from no-finger to finger-down to establish a proper baseline. + * A delay would mean the finger is already on the sensor. */ + fpi_device_report_finger_status_changes ( + dev, FP_FINGER_STATUS_NEEDED, FP_FINGER_STATUS_NONE); + fpi_ssm_next_state (ssm); +} + +static void +enroll_build_capture (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_capture_build_cmd_02 (&self->capture, + self->sensor.type_info, + VALIDITY_CAPTURE_ENROLL, + &cmd_len); + + if (!cmd) + { + fp_warn ("Failed to build enroll capture command"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_capture_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("Capture command failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Now wait for finger-down interrupt */ + fpi_ssm_next_state (ssm); +} + +static void +enroll_wait_scan_complete (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Wait for interrupt type 3 with scan_complete bit. + * Use 30-second timeout: enroll mode scans need proper finger contact. */ + FpiUsbTransfer *transfer; + + fpi_ssm_set_data (ssm, GINT_TO_POINTER (3), NULL); + transfer = fpi_usb_transfer_new (FP_DEVICE (self)); + fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, + VALIDITY_USB_INT_DATA_SIZE); + fpi_usb_transfer_submit (transfer, 30000, + self->interrupt_cancellable, + interrupt_cb, ssm); +} + +static void +enroll_capture_stop_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: no glow_end after capture — LED stays on. */ + if (self->scan_incomplete_count > 0) + /* Incomplete scan: retry after a brief delay. + * glow_start at the top of the loop will reinitialize. + * PY: in the except block, just retries the whole loop. */ + fpi_ssm_jump_to_state_delayed (ssm, ENROLL_LED_ON, 3000); + else + /* Good scan — proceed to enrollment_update_start */ + fpi_ssm_next_state (ssm); +} + +static void +enroll_update_start (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x68: enrollment_update_start(key) */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_enrollment_update_start ( + self->enroll_key, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_update_start_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("enrollment_update_start failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Response: new_key(4LE) */ + if (self->cmd_response_data && self->cmd_response_len >= 4) + self->enroll_key = FP_READ_UINT32_LE (self->cmd_response_data); + + fpi_ssm_next_state (ssm); +} + +static void +enroll_wait_update_start_int (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* PY: usb.wait_int() inside enrollment_update_start() */ + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, + VALIDITY_USB_INT_DATA_SIZE); + fpi_usb_transfer_submit (transfer, 0, + self->interrupt_cancellable, + update_interrupt_cb, ssm); +} + +static void +enroll_db_write_enable (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: write_enable() before 1st enrollment_update */ + gsize blob_len; + const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); + + vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); +} + +static void +enroll_db_write_enable_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("db_write_enable (1st) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +enroll_append_image (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x6B: enrollment_update with current template */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_enrollment_update ( + self->enroll_template, self->enroll_template_len, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_append_image_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* First enrollment_update call — just triggers firmware processing. + * Response is status=OK with len=0; no data to parse here. + * The actual result comes from the second call after the interrupt. */ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("enrollment_update (trigger) non-OK: status=0x%04x — skip to update_end", + self->cmd_response_status); + /* Don't fail — firmware may be rejecting this iteration. + * Skip remaining enrollment_update states and go to UPDATE_END, + * which will proceed to LOOP_CHECK and retry or exit. */ + fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +enroll_cleanups (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: call_cleanups() in finally block of enrollment_update (1st) */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_cleanups_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Status 0x0491 = nothing to commit, which is OK */ + if (self->cmd_response_status != VCSFW_STATUS_OK && + self->cmd_response_status != 0x0491) + fp_warn ("call_cleanups (1st) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +enroll_wait_update_int (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* PY: usb.wait_int() — wait for firmware to finish processing + * the enrollment image before reading the result. */ + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, + VALIDITY_USB_INT_DATA_SIZE); + fpi_usb_transfer_submit (transfer, 0, + self->interrupt_cancellable, + update_interrupt_cb, ssm); +} + +static void +enroll_db_write_enable_read (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: write_enable() before 2nd enrollment_update */ + gsize blob_len; + const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); + + vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); +} + +static void +enroll_db_write_enable_read_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("db_write_enable (2nd) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +enroll_append_image_read (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Second cmd 0x6B: enrollment_update — reads the actual result + * with template/header/tid data. Same payload as the first call. */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_enrollment_update ( + self->enroll_template, self->enroll_template_len, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_append_image_read_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("enrollment_update (read) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Parse the enrollment update response for template/header/tid */ + if (self->cmd_response_data && self->cmd_response_len > 0) + { + EnrollmentUpdateResult result; + + fp_info ("enrollment_update read response: len=%zu", + self->cmd_response_len); + + if (parse_enrollment_update_response (self->cmd_response_data, + self->cmd_response_len, + &result)) + { + /* Update template for next iteration */ + g_clear_pointer (&self->enroll_template, g_free); + if (result.template_data) + { + self->enroll_template = g_steal_pointer (&result.template_data); + self->enroll_template_len = result.template_len; + fp_info (" template: %zu bytes", self->enroll_template_len); + } + + if (result.header) + fp_info (" header: %zu bytes", result.header_len); + + /* If tid is present, enrollment is complete */ + if (result.tid) + { + fp_info (" tid: %zu bytes — enrollment complete!", + result.tid_len); + g_clear_pointer (&self->bulk_data, g_free); + self->bulk_data = g_steal_pointer (&result.tid); + self->bulk_data_len = result.tid_len; + } + + enrollment_update_result_clear (&result); + } + } + else + { + fp_info ("enrollment_update read response: EMPTY (len=0)"); + } + + fpi_ssm_next_state (ssm); +} + +static void +enroll_cleanups_read (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: call_cleanups() in finally block of enrollment_update (2nd) */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_cleanups_read_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK && + self->cmd_response_status != 0x0491) + fp_warn ("call_cleanups (2nd) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +enroll_update_end (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: enrollment_update_end() → pack('enroll_stage++; + + /* Report progress (capped at nr_enroll_stages for the UI) */ + if (self->enroll_stage <= VALIDITY_ENROLL_STAGES) + fpi_device_enroll_progress (dev, self->enroll_stage, NULL, NULL); + + fp_info ("Enrollment stage %u/%u", self->enroll_stage, + VALIDITY_ENROLL_STAGES); + + /* If we have a TID, enrollment is complete. + * PY calls enrollment_update_end twice: once in the finally + * block (ENROLL_UPDATE_END) and once more after the loop. */ + if (self->bulk_data && self->bulk_data_len > 0) + { + fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END2); + return; + } + + /* PY loops indefinitely until TID appears. Use a generous + * upper bound to avoid an infinite loop on broken firmware. */ + if (self->enroll_stage >= 30) + { + fp_warn ("Enrollment did not complete within %u stages", + self->enroll_stage); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + /* Loop back for next stage */ + fpi_ssm_jump_to_state (ssm, ENROLL_LED_ON); +} + +static void +enroll_update_end2 (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: second enrollment_update_end() after the loop. + * Same command: pack('cmd_response_status == 0x04b3) + { + fp_info ("StgWindsor storage not found — creating it"); + fpi_ssm_next_state (ssm); /* → ENROLL_INIT_STORAGE_WE */ + return; + } + + ValidityUserStorage stg = { 0 }; + + if (self->cmd_response_status != VCSFW_STATUS_OK || + !self->cmd_response_data || + !validity_db_parse_user_storage (self->cmd_response_data, + self->cmd_response_len, &stg)) + { + fp_warn ("get_user_storage failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + self->enroll_storage_dbid = stg.dbid; + fp_info ("Storage dbid: %u", stg.dbid); + validity_user_storage_clear (&stg); + /* Skip storage creation states — jump to DB_WRITE_ENABLE2 */ + fpi_ssm_jump_to_state (ssm, ENROLL_DB_WRITE_ENABLE2); +} + +static void +enroll_init_storage_we (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: db.new_user_storate() → new_record(1, 4, 3, 'StgWindsor\0') + * First: db_write_enable */ + gsize blob_len; + const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); + + vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); +} + +static void +enroll_init_storage_we_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("db_write_enable (init_storage) failed: 0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +enroll_init_storage_create (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: db.new_record(1, 4, 3, b'StgWindsor\0') + * parent=1 (root), type=4 (storage), storage=3, data=name */ + const gchar *name = VALIDITY_STORAGE_NAME; + gsize name_len = strlen (name) + 1; /* include NUL */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_new_record ( + 1, 4, 3, (const guint8 *) name, name_len, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_init_storage_create_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("create storage failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fp_info ("StgWindsor storage created successfully"); + fpi_ssm_next_state (ssm); +} + +static void +enroll_init_storage_clean (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_db_write_enable2 (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Enable DB writes for storing the finger record */ + gsize blob_len; + const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); + + vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); +} + +static void +enroll_db_write_enable2_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("db_write_enable for finger creation failed: 0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Proceed to create a new user record with a UUID identity. + * python-validity: usr = db.new_user(identity) */ + fpi_ssm_next_state (ssm); +} + +static void +enroll_create_user (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* Create user with UUID identity via cmd 0x47 (new_record). + * python-validity: usr = db.new_user(identity) + * → new_record(stg.dbid, 5, stg.dbid, identity_to_bytes(identity)) + * + * We generate a UUID for the user identity. */ + + FpPrint *print = NULL; + g_autofree guint8 *identity = NULL; + gsize identity_len; + + fpi_device_get_enroll_data (dev, &print); + + /* Generate a UUID string for this enrollment. + * Use g_uuid_string_random() for a unique per-enrollment identity. */ + g_autofree gchar *user_id = g_uuid_string_random (); + + identity = validity_db_build_identity (user_id, &identity_len); + if (!identity) + { + fp_warn ("Failed to build identity for user '%s'", user_id); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + /* Store user_id in print for later retrieval (e.g. delete) */ + GVariant *data = g_variant_new_string (user_id); + g_object_set_data_full (G_OBJECT (print), "validity-user-id", + g_variant_ref_sink (data), + (GDestroyNotify) g_variant_unref); + + /* cmd 0x47: new_record(parent=storage_dbid, type=5=user, storage=storage_dbid, data=identity) */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_new_record ( + self->enroll_storage_dbid, + VALIDITY_DB_RECORD_TYPE_USER, + self->enroll_storage_dbid, + identity, identity_len, + &cmd_len); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_create_user_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("create user failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Parse the new user record ID — stash for finger creation */ + guint16 user_dbid; + if (self->cmd_response_data && + validity_db_parse_new_record_id (self->cmd_response_data, + self->cmd_response_len, + &user_dbid)) + { + fp_info ("Created user record: dbid=%u", user_dbid); + self->enroll_user_dbid = user_dbid; + } + else + { + fp_warn ("Failed to parse new user record ID"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + fpi_ssm_next_state (ssm); +} + +static void +enroll_create_user_cleanups (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: new_record always calls call_cleanups in finally block */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_db_write_enable3 (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* PY: new_record calls db_write_enable before each cmd 0x47 */ + gsize blob_len; + const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); + + vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); +} + +static void +enroll_db_write_enable3_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("db_write_enable3 failed: 0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +enroll_create_finger (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpPrint *print = NULL; + FpFinger finger; + + fpi_device_get_enroll_data (dev, &print); + finger = fp_print_get_finger (print); + + guint16 subtype = validity_finger_to_subtype (finger); + guint16 user_dbid = self->enroll_user_dbid; + + /* Build finger data from template + tid */ + gsize finger_data_len; + guint8 *finger_data = validity_db_build_finger_data ( + subtype, + self->enroll_template, self->enroll_template_len, + self->bulk_data, self->bulk_data_len, + &finger_data_len); + + /* cmd 0x47: new_record(parent=user, type=0x0b, storage=3, data=finger_data) + * python-validity: type 0xb becomes 0x6 due to db_write_enable magic */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_new_record ( + user_dbid, + 0x0b, /* finger type: becomes 0x06 after db_write_enable */ + self->enroll_storage_dbid, + finger_data, finger_data_len, + &cmd_len); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); + g_free (finger_data); +} + +static void +enroll_create_finger_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("create finger failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + guint16 finger_dbid; + if (self->cmd_response_data && + validity_db_parse_new_record_id (self->cmd_response_data, + self->cmd_response_len, + &finger_dbid)) + fp_info ("Created finger record: dbid=%u", finger_dbid); + + fpi_ssm_next_state (ssm); +} + +static void +enroll_final_cleanups (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +enroll_wait_finger_int (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* PY: usb.wait_int() after new_finger/cleanups — accept any interrupt */ + FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); + + fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, + VALIDITY_USB_INT_DATA_SIZE); + fpi_usb_transfer_submit (transfer, 5000, + self->interrupt_cancellable, + update_interrupt_cb, ssm); +} + +static void +enroll_led_on (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + const guint8 *cmd = validity_capture_glow_start_cmd (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); +} + +static void +enroll_get_prg_status (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */ + const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +enroll_capture_stop (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x04: capture stop/cleanup */ + const guint8 cmd[] = { 0x04 }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +enroll_led_off (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + const guint8 *cmd = validity_capture_glow_end_cmd (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); +} + static void enroll_run_state (FpiSsm *ssm, FpDevice *dev) @@ -301,21 +1228,7 @@ enroll_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case ENROLL_CLEANUP_STALE: - { - /* Close any stale enrollment session before starting fresh. - * Firmware may have leftover state from a previous session - * (e.g. if enrollment was interrupted). Send cmd 0x69 flag=0 - * (enrollment_update_end) — errors are expected and ignored. */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_create_enrollment (FALSE, &cmd_len); - self->enroll_key = 0; - self->enroll_stage = 0; - self->scan_incomplete_count = 0; - g_clear_pointer (&self->enroll_template, g_free); - self->enroll_template_len = 0; - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_cleanup_stale (ssm, self); break; case ENROLL_CLEANUP_STALE_RECV: @@ -324,117 +1237,35 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_PRE_GET_STORAGE: - { - /* Check for existing user records that would cause 0x0526 */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user_storage ( - VALIDITY_STORAGE_NAME, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_pre_get_storage (ssm, self); break; case ENROLL_PRE_GET_STORAGE_RECV: - { - validity_user_storage_clear (&self->list_storage); - - if (self->cmd_response_status != VCSFW_STATUS_OK || - !self->cmd_response_data || - !validity_db_parse_user_storage (self->cmd_response_data, - self->cmd_response_len, - &self->list_storage)) - { - /* No storage or parse error — skip cleanup, go to enrollment */ - fpi_ssm_jump_to_state (ssm, ENROLL_START); - return; - } - - if (self->list_storage.user_count == 0) - { - fp_dbg ("No existing users — skipping pre-enrollment cleanup"); - fpi_ssm_jump_to_state (ssm, ENROLL_START); - return; - } - - fp_info ("Pre-enrollment cleanup: deleting %u existing user(s)", - self->list_storage.user_count); - self->list_user_idx = 0; - fpi_ssm_next_state (ssm); - } + enroll_pre_get_storage_recv (ssm, self); break; case ENROLL_PRE_DEL_USER: - { - if (self->list_user_idx >= self->list_storage.user_count) - { - fp_info ("Pre-enrollment cleanup done"); - validity_user_storage_clear (&self->list_storage); - fpi_ssm_jump_to_state (ssm, ENROLL_START); - return; - } - - guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len); - fp_info ("Deleting user record dbid=%u", user_dbid); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_pre_del_user (ssm, self); break; case ENROLL_PRE_DEL_USER_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("Pre-enrollment del_record(dbid=%u) failed: status=0x%04x", - self->list_storage.user_dbids[self->list_user_idx], - self->cmd_response_status); - - self->list_user_idx++; - fpi_ssm_jump_to_state (ssm, ENROLL_PRE_DEL_USER); - } + enroll_pre_del_user_recv (ssm, self); break; case ENROLL_START: - { - /* cmd 0x69 flag=1: create enrollment session */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_create_enrollment (TRUE, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_start (ssm, self); break; case ENROLL_START_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("create_enrollment failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fpi_ssm_next_state (ssm); - } + enroll_start_recv (ssm, self); break; case ENROLL_LED_ON: - { - gsize cmd_len; - const guint8 *cmd = validity_capture_glow_start_cmd (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - } + enroll_led_on (ssm, self); break; case ENROLL_LED_ON_RECV: - /* LED is on — signal that we need a finger. - * PY sends capture IMMEDIATELY after glow_start_scan(), no delay. - * The ENROLL finger detect (chunk 0x26) needs to see the transition - * from no-finger to finger-down to establish a proper baseline. - * A delay would mean the finger is already on the sensor. */ - fpi_device_report_finger_status_changes ( - dev, FP_FINGER_STATUS_NEEDED, FP_FINGER_STATUS_NONE); - fpi_ssm_next_state (ssm); + enroll_led_on_recv (ssm, self); break; case ENROLL_WAIT_FINGER_DELAY: @@ -443,23 +1274,7 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_BUILD_CAPTURE: - { - gsize cmd_len; - guint8 *cmd = validity_capture_build_cmd_02 (&self->capture, - self->sensor.type_info, - VALIDITY_CAPTURE_ENROLL, - &cmd_len); - if (!cmd) - { - fp_warn ("Failed to build enroll capture command"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_build_capture (ssm, self); break; case ENROLL_CAPTURE_SEND: @@ -468,19 +1283,7 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_CAPTURE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("Capture command failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Now wait for finger-down interrupt */ - fpi_ssm_next_state (ssm); - } + enroll_capture_recv (ssm, self); break; case ENROLL_WAIT_FINGER: @@ -489,26 +1292,11 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_WAIT_SCAN_COMPLETE: - { - /* Wait for interrupt type 3 with scan_complete bit. - * Use 30-second timeout: enroll mode scans need proper finger contact. */ - FpiUsbTransfer *transfer; - fpi_ssm_set_data (ssm, GINT_TO_POINTER (3), NULL); - transfer = fpi_usb_transfer_new (FP_DEVICE (self)); - fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, - VALIDITY_USB_INT_DATA_SIZE); - fpi_usb_transfer_submit (transfer, 30000, - self->interrupt_cancellable, - interrupt_cb, ssm); - } + enroll_wait_scan_complete (ssm, self); break; case ENROLL_GET_PRG_STATUS: - { - /* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */ - const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + enroll_get_prg_status (ssm, self); break; case ENROLL_GET_PRG_STATUS_RECV: @@ -517,266 +1305,79 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_CAPTURE_STOP: - { - /* cmd 0x04: capture stop/cleanup */ - const guint8 cmd[] = { 0x04 }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + enroll_capture_stop (ssm, self); break; case ENROLL_CAPTURE_STOP_RECV: - /* PY: no glow_end after capture — LED stays on. */ - if (self->scan_incomplete_count > 0) - /* Incomplete scan: retry after a brief delay. - * glow_start at the top of the loop will reinitialize. - * PY: in the except block, just retries the whole loop. */ - fpi_ssm_jump_to_state_delayed (ssm, ENROLL_LED_ON, 3000); - else - /* Good scan — proceed to enrollment_update_start */ - fpi_ssm_next_state (ssm); + enroll_capture_stop_recv (ssm, self); break; case ENROLL_UPDATE_START: - { - /* cmd 0x68: enrollment_update_start(key) */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_enrollment_update_start ( - self->enroll_key, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_update_start (ssm, self); break; case ENROLL_UPDATE_START_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("enrollment_update_start failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Response: new_key(4LE) */ - if (self->cmd_response_data && self->cmd_response_len >= 4) - self->enroll_key = FP_READ_UINT32_LE (self->cmd_response_data); - - fpi_ssm_next_state (ssm); - } + enroll_update_start_recv (ssm, self); break; case ENROLL_WAIT_UPDATE_START_INT: - { - /* PY: usb.wait_int() inside enrollment_update_start() */ - FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); - fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, - VALIDITY_USB_INT_DATA_SIZE); - fpi_usb_transfer_submit (transfer, 0, - self->interrupt_cancellable, - update_interrupt_cb, ssm); - } + enroll_wait_update_start_int (ssm, self); break; case ENROLL_DB_WRITE_ENABLE: - { - /* PY: write_enable() before 1st enrollment_update */ - gsize blob_len; - const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); - vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); - } + enroll_db_write_enable (ssm, self); break; case ENROLL_DB_WRITE_ENABLE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("db_write_enable (1st) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + enroll_db_write_enable_recv (ssm, self); break; case ENROLL_APPEND_IMAGE: - { - /* cmd 0x6B: enrollment_update with current template */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_enrollment_update ( - self->enroll_template, self->enroll_template_len, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_append_image (ssm, self); break; case ENROLL_APPEND_IMAGE_RECV: - { - /* First enrollment_update call — just triggers firmware processing. - * Response is status=OK with len=0; no data to parse here. - * The actual result comes from the second call after the interrupt. */ - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("enrollment_update (trigger) non-OK: status=0x%04x — skip to update_end", - self->cmd_response_status); - /* Don't fail — firmware may be rejecting this iteration. - * Skip remaining enrollment_update states and go to UPDATE_END, - * which will proceed to LOOP_CHECK and retry or exit. */ - fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END); - return; - } - fpi_ssm_next_state (ssm); - } + enroll_append_image_recv (ssm, self); break; case ENROLL_CLEANUPS: - { - /* PY: call_cleanups() in finally block of enrollment_update (1st) */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_cleanups (ssm, self); break; case ENROLL_CLEANUPS_RECV: - { - /* Status 0x0491 = nothing to commit, which is OK */ - if (self->cmd_response_status != VCSFW_STATUS_OK && - self->cmd_response_status != 0x0491) - fp_warn ("call_cleanups (1st) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + enroll_cleanups_recv (ssm, self); break; case ENROLL_WAIT_UPDATE_INT: - { - /* PY: usb.wait_int() — wait for firmware to finish processing - * the enrollment image before reading the result. */ - FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); - fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, - VALIDITY_USB_INT_DATA_SIZE); - fpi_usb_transfer_submit (transfer, 0, - self->interrupt_cancellable, - update_interrupt_cb, ssm); - } + enroll_wait_update_int (ssm, self); break; case ENROLL_DB_WRITE_ENABLE_READ: - { - /* PY: write_enable() before 2nd enrollment_update */ - gsize blob_len; - const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); - vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); - } + enroll_db_write_enable_read (ssm, self); break; case ENROLL_DB_WRITE_ENABLE_READ_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("db_write_enable (2nd) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + enroll_db_write_enable_read_recv (ssm, self); break; case ENROLL_APPEND_IMAGE_READ: - { - /* Second cmd 0x6B: enrollment_update — reads the actual result - * with template/header/tid data. Same payload as the first call. */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_enrollment_update ( - self->enroll_template, self->enroll_template_len, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_append_image_read (ssm, self); break; case ENROLL_APPEND_IMAGE_READ_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("enrollment_update (read) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Parse the enrollment update response for template/header/tid */ - if (self->cmd_response_data && self->cmd_response_len > 0) - { - EnrollmentUpdateResult result; - - fp_info ("enrollment_update read response: len=%zu", - self->cmd_response_len); - - if (parse_enrollment_update_response (self->cmd_response_data, - self->cmd_response_len, - &result)) - { - /* Update template for next iteration */ - g_clear_pointer (&self->enroll_template, g_free); - if (result.template_data) - { - self->enroll_template = g_steal_pointer (&result.template_data); - self->enroll_template_len = result.template_len; - fp_info (" template: %zu bytes", self->enroll_template_len); - } - - if (result.header) - fp_info (" header: %zu bytes", result.header_len); - - /* If tid is present, enrollment is complete */ - if (result.tid) - { - fp_info (" tid: %zu bytes — enrollment complete!", - result.tid_len); - g_clear_pointer (&self->bulk_data, g_free); - self->bulk_data = g_steal_pointer (&result.tid); - self->bulk_data_len = result.tid_len; - } - - enrollment_update_result_clear (&result); - } - } - else - { - fp_info ("enrollment_update read response: EMPTY (len=0)"); - } - - fpi_ssm_next_state (ssm); - } + enroll_append_image_read_recv (ssm, self); break; case ENROLL_CLEANUPS_READ: - { - /* PY: call_cleanups() in finally block of enrollment_update (2nd) */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_cleanups_read (ssm, self); break; case ENROLL_CLEANUPS_READ_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK && - self->cmd_response_status != 0x0491) - fp_warn ("call_cleanups (2nd) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + enroll_cleanups_read_recv (ssm, self); break; case ENROLL_UPDATE_END: - { - /* PY: enrollment_update_end() → pack('enroll_stage++; - - /* Report progress (capped at nr_enroll_stages for the UI) */ - if (self->enroll_stage <= VALIDITY_ENROLL_STAGES) - fpi_device_enroll_progress (dev, self->enroll_stage, NULL, NULL); - - fp_info ("Enrollment stage %u/%u", self->enroll_stage, - VALIDITY_ENROLL_STAGES); - - /* If we have a TID, enrollment is complete. - * PY calls enrollment_update_end twice: once in the finally - * block (ENROLL_UPDATE_END) and once more after the loop. */ - if (self->bulk_data && self->bulk_data_len > 0) - { - fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END2); - return; - } - - /* PY loops indefinitely until TID appears. Use a generous - * upper bound to avoid an infinite loop on broken firmware. */ - if (self->enroll_stage >= 30) - { - fp_warn ("Enrollment did not complete within %u stages", - self->enroll_stage); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - /* Loop back for next stage */ - fpi_ssm_jump_to_state (ssm, ENROLL_LED_ON); - } + enroll_loop_check (ssm, self); break; case ENROLL_UPDATE_END2: - { - /* PY: second enrollment_update_end() after the loop. - * Same command: pack('cmd_response_status == 0x04b3) - { - fp_info ("StgWindsor storage not found — creating it"); - fpi_ssm_next_state (ssm); /* → ENROLL_INIT_STORAGE_WE */ - return; - } - - ValidityUserStorage stg = { 0 }; - - if (self->cmd_response_status != VCSFW_STATUS_OK || - !self->cmd_response_data || - !validity_db_parse_user_storage (self->cmd_response_data, - self->cmd_response_len, &stg)) - { - fp_warn ("get_user_storage failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - self->enroll_storage_dbid = stg.dbid; - fp_info ("Storage dbid: %u", stg.dbid); - validity_user_storage_clear (&stg); - /* Skip storage creation states — jump to DB_WRITE_ENABLE2 */ - fpi_ssm_jump_to_state (ssm, ENROLL_DB_WRITE_ENABLE2); - } + enroll_get_storage_recv (ssm, self); break; case ENROLL_INIT_STORAGE_WE: - { - /* PY: db.new_user_storate() → new_record(1, 4, 3, 'StgWindsor\0') - * First: db_write_enable */ - gsize blob_len; - const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); - vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); - } + enroll_init_storage_we (ssm, self); break; case ENROLL_INIT_STORAGE_WE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("db_write_enable (init_storage) failed: 0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + enroll_init_storage_we_recv (ssm, self); break; case ENROLL_INIT_STORAGE_CREATE: - { - /* PY: db.new_record(1, 4, 3, b'StgWindsor\0') - * parent=1 (root), type=4 (storage), storage=3, data=name */ - const gchar *name = VALIDITY_STORAGE_NAME; - gsize name_len = strlen (name) + 1; /* include NUL */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_new_record ( - 1, 4, 3, (const guint8 *) name, name_len, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_init_storage_create (ssm, self); break; case ENROLL_INIT_STORAGE_CREATE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("create storage failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fp_info ("StgWindsor storage created successfully"); - fpi_ssm_next_state (ssm); - } + enroll_init_storage_create_recv (ssm, self); break; case ENROLL_INIT_STORAGE_CLEAN: - { - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_init_storage_clean (ssm, self); break; case ENROLL_INIT_STORAGE_CLEAN_RECV: - { - /* Now retry get_user_storage to get the dbid */ - fpi_ssm_jump_to_state (ssm, ENROLL_GET_STORAGE); - } + /* Now retry get_user_storage to get the dbid */ + fpi_ssm_jump_to_state (ssm, ENROLL_GET_STORAGE); break; case ENROLL_DB_WRITE_ENABLE2: - { - /* Enable DB writes for storing the finger record */ - gsize blob_len; - const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); - vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); - } + enroll_db_write_enable2 (ssm, self); break; case ENROLL_DB_WRITE_ENABLE2_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("db_write_enable for finger creation failed: 0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Proceed to create a new user record with a UUID identity. - * python-validity: usr = db.new_user(identity) */ - fpi_ssm_next_state (ssm); - } + enroll_db_write_enable2_recv (ssm, self); break; case ENROLL_CREATE_USER: - { - /* Create user with UUID identity via cmd 0x47 (new_record). - * python-validity: usr = db.new_user(identity) - * → new_record(stg.dbid, 5, stg.dbid, identity_to_bytes(identity)) - * - * We generate a UUID for the user identity. */ - - FpPrint *print = NULL; - g_autofree guint8 *identity = NULL; - gsize identity_len; - - fpi_device_get_enroll_data (dev, &print); - - /* Generate a UUID string for this enrollment. - * Use g_uuid_string_random() for a unique per-enrollment identity. */ - g_autofree gchar *user_id = g_uuid_string_random (); - - identity = validity_db_build_identity (user_id, &identity_len); - if (!identity) - { - fp_warn ("Failed to build identity for user '%s'", user_id); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - /* Store user_id in print for later retrieval (e.g. delete) */ - GVariant *data = g_variant_new_string (user_id); - g_object_set_data_full (G_OBJECT (print), "validity-user-id", - g_variant_ref_sink (data), - (GDestroyNotify) g_variant_unref); - - /* cmd 0x47: new_record(parent=storage_dbid, type=5=user, storage=storage_dbid, data=identity) */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_new_record ( - self->enroll_storage_dbid, - VALIDITY_DB_RECORD_TYPE_USER, - self->enroll_storage_dbid, - identity, identity_len, - &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_create_user (ssm, self); break; case ENROLL_CREATE_USER_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("create user failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Parse the new user record ID — stash for finger creation */ - guint16 user_dbid; - if (self->cmd_response_data && - validity_db_parse_new_record_id (self->cmd_response_data, - self->cmd_response_len, - &user_dbid)) - { - fp_info ("Created user record: dbid=%u", user_dbid); - self->enroll_user_dbid = user_dbid; - } - else - { - fp_warn ("Failed to parse new user record ID"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - fpi_ssm_next_state (ssm); - } + enroll_create_user_recv (ssm, self); break; case ENROLL_CREATE_USER_CLEANUPS: - { - /* PY: new_record always calls call_cleanups in finally block */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_create_user_cleanups (ssm, self); break; case ENROLL_CREATE_USER_CLEANUPS_RECV: @@ -1066,91 +1455,23 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_DB_WRITE_ENABLE3: - { - /* PY: new_record calls db_write_enable before each cmd 0x47 */ - gsize blob_len; - const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len); - vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL); - } + enroll_db_write_enable3 (ssm, self); break; case ENROLL_DB_WRITE_ENABLE3_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("db_write_enable3 failed: 0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fpi_ssm_next_state (ssm); - } + enroll_db_write_enable3_recv (ssm, self); break; case ENROLL_CREATE_FINGER: - { - FpPrint *print = NULL; - FpFinger finger; - - fpi_device_get_enroll_data (dev, &print); - finger = fp_print_get_finger (print); - - guint16 subtype = validity_finger_to_subtype (finger); - guint16 user_dbid = self->enroll_user_dbid; - - /* Build finger data from template + tid */ - gsize finger_data_len; - guint8 *finger_data = validity_db_build_finger_data ( - subtype, - self->enroll_template, self->enroll_template_len, - self->bulk_data, self->bulk_data_len, - &finger_data_len); - - /* cmd 0x47: new_record(parent=user, type=0x0b, storage=3, data=finger_data) - * python-validity: type 0xb becomes 0x6 due to db_write_enable magic */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_new_record ( - user_dbid, - 0x0b, /* finger type: becomes 0x06 after db_write_enable */ - self->enroll_storage_dbid, - finger_data, finger_data_len, - &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - g_free (finger_data); - } + enroll_create_finger (ssm, self); break; case ENROLL_CREATE_FINGER_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("create finger failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - guint16 finger_dbid; - if (self->cmd_response_data && - validity_db_parse_new_record_id (self->cmd_response_data, - self->cmd_response_len, - &finger_dbid)) - fp_info ("Created finger record: dbid=%u", finger_dbid); - - fpi_ssm_next_state (ssm); - } + enroll_create_finger_recv (ssm, self); break; case ENROLL_FINAL_CLEANUPS: - { - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + enroll_final_cleanups (ssm, self); break; case ENROLL_FINAL_CLEANUPS_RECV: @@ -1158,23 +1479,11 @@ enroll_run_state (FpiSsm *ssm, break; case ENROLL_WAIT_FINGER_INT: - { - /* PY: usb.wait_int() after new_finger/cleanups — accept any interrupt */ - FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev); - fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN, - VALIDITY_USB_INT_DATA_SIZE); - fpi_usb_transfer_submit (transfer, 5000, - self->interrupt_cancellable, - update_interrupt_cb, ssm); - } + enroll_wait_finger_int (ssm, self); break; case ENROLL_LED_OFF: - { - gsize cmd_len; - const guint8 *cmd = validity_capture_glow_end_cmd (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - } + enroll_led_off (ssm, self); break; case ENROLL_LED_OFF_RECV: diff --git a/libfprint/drivers/validity/validity_fwext.c b/libfprint/drivers/validity/validity_fwext.c index 9fb49fee..33a82018 100644 --- a/libfprint/drivers/validity/validity_fwext.c +++ b/libfprint/drivers/validity/validity_fwext.c @@ -376,297 +376,404 @@ fwext_upload_data_free (gpointer data) g_free (ud); } + +static void +fwext_send_write_hw_reg (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + 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); +} + +static void +fwext_recv_write_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, + "WRITE_HW_REG failed: status=0x%04x", + self->cmd_response_status)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +fwext_send_read_hw_reg (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + 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); +} + +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) +{ + FwextUploadData *ud = fpi_ssm_get_data (ssm); + gsize dbe_len; + const guint8 *dbe = validity_fwext_get_db_write_enable (ud->vid, + ud->pid, + &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 blob for " + "%04x:%04x", ud->vid, ud->pid)); + return; + } + + vcsfw_tls_cmd_send (self, ssm, dbe, dbe_len, NULL); +} + +static void +fwext_recv_db_write_enable (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, + "db_write_enable failed: " + "status=0x%04x", + self->cmd_response_status)); + return; + } + fpi_ssm_next_state (ssm); +} + +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_write_chunk (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, + "WRITE_FLASH chunk failed: " + "status=0x%04x", + self->cmd_response_status)); + return; + } + fpi_ssm_next_state (ssm); +} + +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_write_signature (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, + "WRITE_FW_SIG failed: " + "status=0x%04x", + self->cmd_response_status)); + return; + } + fpi_ssm_next_state (ssm); +} + +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); +} + +static void +fwext_send_reboot (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + 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); +} + +static void +fwext_recv_reboot (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Sensor will disconnect and re-enumerate on USB. + * We mark SSM completed -- the caller (open sequence) + * handles the post-reboot re-init. */ + fp_info ("FWEXT: Reboot sent. Device will re-enumerate."); + fpi_ssm_mark_completed (ssm); +} + +static void +fwext_send_cleanup (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + guint8 cmd[] = { VCSFW_CMD_CLEANUP }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +fwext_send_verify (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, FWEXT_PARTITION }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + void validity_fwext_upload_run_state (FpiSsm *ssm, FpDevice *dev) { FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - FwextUploadData *ud = fpi_ssm_get_data (ssm); 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); - } + fwext_send_write_hw_reg (ssm, self); 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)); - return; - } - fpi_ssm_next_state (ssm); + fwext_recv_write_hw_reg (ssm, self); 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); - } + fwext_send_read_hw_reg (ssm, self); break; case FWEXT_RECV_READ_HW_REG: - { - 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); - } + fwext_recv_read_hw_reg (ssm, self); break; case FWEXT_LOAD_FILE: - { - 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); - } + fwext_load_file (ssm, self); break; case FWEXT_SEND_DB_WRITE_ENABLE: - { - gsize dbe_len; - const guint8 *dbe = validity_fwext_get_db_write_enable (ud->vid, - ud->pid, - &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 blob for " - "%04x:%04x", ud->vid, ud->pid)); - return; - } - - vcsfw_tls_cmd_send (self, ssm, dbe, dbe_len, NULL); - } + 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)); - return; - } - fpi_ssm_next_state (ssm); + fwext_recv_db_write_enable (ssm, self); break; case FWEXT_SEND_WRITE_CHUNK: - { - 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); - } + 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)); - return; - } - fpi_ssm_next_state (ssm); + fwext_recv_write_chunk (ssm, self); break; case FWEXT_SEND_CLEANUP: - { - guint8 cmd[] = { VCSFW_CMD_CLEANUP }; - - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + fwext_send_cleanup (ssm, self); break; case FWEXT_RECV_CLEANUP: - /* 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); + fwext_recv_cleanup (ssm, self); break; case FWEXT_SEND_WRITE_SIGNATURE: - { - 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); - } + 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)); - return; - } - fpi_ssm_next_state (ssm); + fwext_recv_write_signature (ssm, self); break; case FWEXT_SEND_VERIFY: - { - guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, FWEXT_PARTITION }; - - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + fwext_send_verify (ssm, self); break; case FWEXT_RECV_VERIFY: - { - 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); - } + 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); - } + fwext_send_reboot (ssm, self); break; case FWEXT_RECV_REBOOT: - /* Sensor will disconnect and re-enumerate on USB. - * We mark SSM completed -- the caller (open sequence) - * handles the post-reboot re-init. */ - fp_info ("FWEXT: Reboot sent. Device will re-enumerate."); - fpi_ssm_mark_completed (ssm); + fwext_recv_reboot (ssm, self); break; } } diff --git a/libfprint/drivers/validity/validity_pair.c b/libfprint/drivers/validity/validity_pair.c index 018e8930..978cb830 100644 --- a/libfprint/drivers/validity/validity_pair.c +++ b/libfprint/drivers/validity/validity_pair.c @@ -615,23 +615,752 @@ validity_pair_build_tls_flash (const ValidityPairState *state, * Uses raw USB for pre-TLS phase and TLS-wrapped for post-TLS. * ================================================================ */ + +static void +pair_check_needed (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Parse CMD 0x3e response: status(2) + data */ + if (!self->cmd_response_data || + self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("GET_FLASH_INFO failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + if (!validity_pair_parse_flash_info (self->cmd_response_data, + self->cmd_response_len, + &ps->flash_ic, + &ps->num_partitions)) + { + fp_warn ("Failed to parse flash info"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + if (ps->num_partitions > 0) + { + fp_info ("Flash has %u partitions — verifying TLS keys", + ps->num_partitions); + /* Read flash partition 1 to check if TLS keys exist. + * If they do, pairing is complete. If not, we re-pair. */ + fpi_ssm_next_state (ssm); + return; + } + + fp_info ("Flash has 0 partitions — device needs pairing"); + + /* Look up device descriptor */ + ps->dev_desc = validity_hal_device_lookup (self->dev_type); + if (!ps->dev_desc) + { + fp_warn ("No HAL descriptor for dev_type=%u", self->dev_type); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + /* No partitions — skip TLS verify, go straight to pairing */ + fpi_ssm_jump_to_state (ssm, PAIR_SEND_RESET_BLOB); +} + +static void +pair_verify_tls_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Read flash partition 1 (TLS cert store) to verify keys exist */ + guint8 cmd[13]; + + cmd[0] = VCSFW_CMD_READ_FLASH; + cmd[1] = 0x01; /* partition */ + cmd[2] = 0x01; /* access flag */ + FP_WRITE_UINT16_LE (&cmd[3], 0x0000); + FP_WRITE_UINT32_LE (&cmd[5], 0x0000); + FP_WRITE_UINT32_LE (&cmd[9], 0x1000); + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_verify_tls_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Check if TLS flash has valid key data */ + gboolean have_keys = FALSE; + + if (self->cmd_response_status == VCSFW_STATUS_OK && + self->cmd_response_data && self->cmd_response_len > 6) + { + guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data); + const guint8 *flash_data = self->cmd_response_data + 6; + gsize flash_avail = self->cmd_response_len - 6; + + if (flash_sz > flash_avail) + flash_sz = flash_avail; + + /* Quick check: scan for block IDs 3 (cert), 4 (privkey), 6 (ecdh) */ + const guint8 *pos = flash_data; + gsize remaining = flash_sz; + gboolean found_priv = FALSE, found_ecdh = FALSE, found_cert = FALSE; + + while (remaining >= 36) /* header(4) + hash(32) */ + { + guint16 block_id = FP_READ_UINT16_LE (pos); + guint16 block_size = FP_READ_UINT16_LE (pos + 2); + + if (block_id == 0xFFFF) + break; + + pos += 36; /* skip header + hash */ + remaining -= 36; + + if (block_size > remaining) + break; + + if (block_id == 4) + found_priv = TRUE; + if (block_id == 6) + found_ecdh = TRUE; + if (block_id == 3) + found_cert = TRUE; + + pos += block_size; + remaining -= block_size; + } + + have_keys = found_priv && found_ecdh && found_cert; + } + + if (have_keys) + { + fp_info ("TLS keys verified on flash — pairing not needed"); + fpi_ssm_jump_to_state (ssm, PAIR_DONE); + return; + } + + fp_info ("TLS keys missing from flash — starting pairing"); + + /* Look up device descriptor */ + ps->dev_desc = validity_hal_device_lookup (self->dev_type); + if (!ps->dev_desc) + { + fp_warn ("No HAL descriptor for dev_type=%u", self->dev_type); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + fpi_ssm_next_state (ssm); +} + +static void +pair_send_reset_blob (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Send reset_blob via raw USB (python-validity: usb.cmd(reset_blob)) */ + if (!ps->dev_desc->reset_blob || ps->dev_desc->reset_blob_len == 0) + { + fp_warn ("No reset_blob available for this device"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); + return; + } + + vcsfw_cmd_send (self, ssm, + ps->dev_desc->reset_blob, + ps->dev_desc->reset_blob_len, NULL); +} + +static void +pair_send_reset_blob_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("reset_blob failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +pair_generate_keys (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Generate ECDH P-256 key pair */ + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_EC, NULL); + + EVP_PKEY_keygen_init (pctx); + EVP_PKEY_CTX_set_ec_paramgen_curve_nid (pctx, NID_X9_62_prime256v1); + EVP_PKEY_keygen (pctx, &ps->client_key); + EVP_PKEY_CTX_free (pctx); + + if (!ps->client_key) + { + fp_warn ("ECDH key generation failed"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + fp_info ("Generated ECDH client key pair"); + fpi_ssm_next_state (ssm); +} + +static void +pair_partition_flash_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Extract public key coordinates (little-endian) */ + BIGNUM *pub_x_bn = NULL, *pub_y_bn = NULL; + + EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x_bn); + EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y_bn); + + guint8 pub_x_be[32], pub_y_be[32]; + guint8 pub_x_le[32], pub_y_le[32]; + + BN_bn2binpad (pub_x_bn, pub_x_be, 32); + BN_bn2binpad (pub_y_bn, pub_y_be, 32); + BN_free (pub_x_bn); + BN_free (pub_y_bn); + + /* Convert big-endian → little-endian (python-validity uses LE) */ + for (int i = 0; i < 32; i++) + { + pub_x_le[i] = pub_x_be[31 - i]; + pub_y_le[i] = pub_y_be[31 - i]; + } + + /* Build CMD 0x4f */ + gsize cmd_len; + g_autofree guint8 *cmd = validity_pair_build_partition_flash_cmd ( + &ps->flash_ic, + ps->dev_desc->flash_layout, + pub_x_le, pub_y_le, + &cmd_len); + + if (!cmd) + { + fp_warn ("Failed to build partition_flash command"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + fp_info ("Sending partition_flash (CMD 0x4f): %" G_GSIZE_FORMAT " bytes", + cmd_len); + + /* NOTE: partition_flash is sent via raw USB (TLS not yet active). + * python-validity sends it through tls.cmd() which falls back to + * raw USB when secure_rx/secure_tx are false. */ + vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL); +} + +static void +pair_partition_flash_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + if (self->cmd_response_status == 0x0404) + { + /* 0x0404 = partitions already exist (half-initialized device). + * Factory reset will wipe flash, then reboot. Next device open + * will start with a clean slate and full pairing will succeed. */ + fp_info ("Flash already partitioned (0x0404) — factory reset needed"); + fpi_ssm_next_state (ssm); + return; + } + + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("partition_flash failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Response: [cert_len:4LE][cert_data:cert_len][...] */ + if (self->cmd_response_data && self->cmd_response_len >= 4) + { + guint32 cert_len = FP_READ_UINT32_LE (self->cmd_response_data); + if (cert_len <= self->cmd_response_len - 4) + { + ps->server_cert = g_memdup2 (self->cmd_response_data + 4, + cert_len); + ps->server_cert_len = cert_len; + fp_info ("Received server certificate: %u bytes", cert_len); + } + } + + /* Skip factory reset states — go straight to CMD50 */ + fpi_ssm_jump_to_state (ssm, PAIR_CMD50_SEND); +} + +static void +pair_factory_reset_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* CMD 0x10 + 0x61 zero bytes: wipes flash partition table. + * python-validity: usb.cmd(b'\x10' + b'\0' * 0x61) */ + guint8 cmd[98]; + + memset (cmd, 0, sizeof (cmd)); + cmd[0] = 0x10; + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_factory_reset_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("Factory reset cmd 0x10 status=0x%04x", + self->cmd_response_status); + else + fp_info ("Factory reset complete — rebooting sensor"); + + /* Reboot; next device open will pair from clean state */ + fpi_ssm_jump_to_state (ssm, PAIR_REBOOT_SEND); +} + +static void +pair_cmd50_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* CMD 0x50: get ECDH server parameters + * python-validity: usb.cmd(unhex('50')) */ + guint8 cmd[] = { VCSFW_CMD_GET_ECDH }; + + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_cmd50_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("CMD 0x50 failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + fpi_ssm_next_state (ssm); +} + +static void +pair_cmd50_process (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Response: [length:4LE][zeros:...][ecdh_blob:400] + * python-validity: + * l, = unpack('cmd_response_data || self->cmd_response_len < 404) + { + fp_warn ("CMD 0x50 response too short: %" G_GSIZE_FORMAT, + self->cmd_response_len); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + guint32 resp_len = FP_READ_UINT32_LE (self->cmd_response_data); + const guint8 *ecdh_data = self->cmd_response_data + + self->cmd_response_len - 400; + + fp_info ("CMD 0x50 response: declared_len=%u, actual=%" G_GSIZE_FORMAT, + resp_len, self->cmd_response_len); + + /* Store ECDH blob: handle_ecdh stores raw blob, extracts pubkey. + * We store it for TLS flash persistence and set up tls.ecdh_q + * via the existing validity_tls code path. */ + ps->ecdh_blob = g_memdup2 (ecdh_data, 400); + ps->ecdh_blob_len = 400; + + /* Parse ECDH blob to extract server public key. + * This sets self->tls.ecdh_q and ecdh_blob. */ + self->tls.ecdh_blob = g_memdup2 (ecdh_data, 400); + self->tls.ecdh_blob_len = 400; + + /* Extract X,Y coordinates from ECDH blob for ecdh_q. + * Format: [header:8][x:32 LE][padding:36][y:32 LE][...] */ + const guint8 *x_le = ecdh_data + TLS_ECDH_X_OFFSET; + const guint8 *y_le = ecdh_data + TLS_ECDH_Y_OFFSET; + + guint8 x_be[32], y_be[32]; + for (int i = 0; i < 32; i++) + { + x_be[i] = x_le[31 - i]; + y_be[i] = y_le[31 - i]; + } + + /* Build ECDH server public key */ + OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new (); + OSSL_PARAM_BLD_push_utf8_string (bld, OSSL_PKEY_PARAM_GROUP_NAME, + "prime256v1", 0); + guint8 pub_uncompressed[65]; + pub_uncompressed[0] = 0x04; + memcpy (pub_uncompressed + 1, x_be, 32); + memcpy (pub_uncompressed + 33, y_be, 32); + OSSL_PARAM_BLD_push_octet_string (bld, OSSL_PKEY_PARAM_PUB_KEY, + pub_uncompressed, 65); + + OSSL_PARAM *params = OSSL_PARAM_BLD_to_param (bld); + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL); + EVP_PKEY_fromdata_init (ctx); + EVP_PKEY_fromdata (ctx, &self->tls.ecdh_q, EVP_PKEY_PUBLIC_KEY, params); + EVP_PKEY_CTX_free (ctx); + OSSL_PARAM_free (params); + OSSL_PARAM_BLD_free (bld); + + if (!self->tls.ecdh_q) + { + fp_warn ("Failed to build ECDH server public key"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + /* Encrypt client private key → priv_blob (handle_priv) + * python-validity: tls.handle_priv(encrypt_key(client_private, client_public)) */ + + /* First, derive PSK if not already done */ + validity_tls_derive_psk (&self->tls); + + /* Extract private key scalar (little-endian) */ + BIGNUM *priv_bn = NULL; + EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_PRIV_KEY, &priv_bn); + + guint8 priv_be[32], priv_le[32]; + BN_bn2binpad (priv_bn, priv_be, 32); + BN_free (priv_bn); + + for (int i = 0; i < 32; i++) + priv_le[i] = priv_be[31 - i]; + + /* Get public key LE coords */ + BIGNUM *pub_x_bn = NULL, *pub_y_bn = NULL; + EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x_bn); + EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y_bn); + + guint8 pub_x_be2[32], pub_y_be2[32]; + guint8 pub_x_le2[32], pub_y_le2[32]; + BN_bn2binpad (pub_x_bn, pub_x_be2, 32); + BN_bn2binpad (pub_y_bn, pub_y_be2, 32); + BN_free (pub_x_bn); + BN_free (pub_y_bn); + + for (int i = 0; i < 32; i++) + { + pub_x_le2[i] = pub_x_be2[31 - i]; + pub_y_le2[i] = pub_y_be2[31 - i]; + } + + gsize priv_blob_len; + ps->priv_blob = validity_pair_encrypt_key (priv_le, pub_x_le2, pub_y_le2, + self->tls.psk_encryption_key, + self->tls.psk_validation_key, + &priv_blob_len); + ps->priv_blob_len = priv_blob_len; + + /* Also store in TLS state for handle_priv path */ + self->tls.priv_blob = g_memdup2 (ps->priv_blob, ps->priv_blob_len); + self->tls.priv_blob_len = ps->priv_blob_len; + + /* Store server cert in TLS state too */ + if (ps->server_cert) + { + self->tls.tls_cert = g_memdup2 (ps->server_cert, ps->server_cert_len); + self->tls.tls_cert_len = ps->server_cert_len; + } + + /* Set priv_key — the TLS handshake needs the actual EC private key + * (EVP_PKEY*) to sign cert_verify. We have it as ps->client_key. */ + if (self->tls.priv_key) + EVP_PKEY_free (self->tls.priv_key); + self->tls.priv_key = EVP_PKEY_dup (ps->client_key); + + OPENSSL_cleanse (priv_le, sizeof (priv_le)); + OPENSSL_cleanse (priv_be, sizeof (priv_be)); + + fp_info ("ECDH exchange complete, private key encrypted"); + fpi_ssm_next_state (ssm); +} + +static void +pair_cleanups_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* CMD 0x1a: call_cleanups after CMD 0x50 + * python-validity: call_cleanups() in finally block */ + guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; + + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_cleanups_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Ignore "nothing to commit" (0x0491) status */ + if (self->cmd_response_status != VCSFW_STATUS_OK && + self->cmd_response_status != 0x0491) + fp_warn ("cleanups failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); + + return; +} + +/* ---- Phase 2: TLS handshake ---- */ + +static void +pair_tls_handshake (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + /* Establish TLS session — python-validity: tls.open() + * Uses subsm: tls_ssm completion/failure propagates to pair SSM. + * NOTE: do NOT overwrite self->open_ssm here — it must remain + * pointing to the open SSM for pair_ssm_done to work. */ + FpiSsm *tls_ssm = fpi_ssm_new (dev, + validity_tls_handshake_run_state, + TLS_HS_NUM_STATES); + + fpi_ssm_start_subsm (ssm, tls_ssm); + + return; +} + +/* ---- Phase 3: Flash erase (TLS-wrapped) ---- */ + +static void +pair_erase_dbe_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Send db_write_enable before each erase + * python-validity: erase_flash() → tls.cmd(db_write_enable) */ + fp_info ("Erasing partition %u (step %u/%u)", + pair_erase_partition_ids[ps->erase_step], + ps->erase_step + 1, + (guint) VALIDITY_PAIR_NUM_ERASE_STEPS); + + vcsfw_tls_cmd_send (self, ssm, + ps->dev_desc->db_write_enable, + ps->dev_desc->db_write_enable_len, NULL); +} + +static void +pair_erase_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* CMD 0x3f: erase partition */ + guint8 cmd[2]; + + cmd[0] = VCSFW_CMD_ERASE_FLASH; + cmd[1] = pair_erase_partition_ids[ps->erase_step]; + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_erase_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("erase partition %u failed: status=0x%04x", + pair_erase_partition_ids[ps->erase_step], + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +pair_erase_clean_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + + /* Ignore "nothing to commit" */ + if (self->cmd_response_status != VCSFW_STATUS_OK && + self->cmd_response_status != 0x0491) + fp_warn ("post-erase cleanups status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); +} + +static void +pair_erase_loop (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + ps->erase_step++; + if (ps->erase_step < VALIDITY_PAIR_NUM_ERASE_STEPS) + { + fpi_ssm_jump_to_state (ssm, PAIR_ERASE_DBE_SEND); + return; + } + fpi_ssm_next_state (ssm); + + return; +} + +/* ---- Phase 4: Write TLS flash (TLS-wrapped) ---- */ + +static void +pair_write_dbe_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* db_write_enable before write_flash */ + vcsfw_tls_cmd_send (self, ssm, + ps->dev_desc->db_write_enable, + ps->dev_desc->db_write_enable_len, NULL); +} + +static void +pair_write_flash_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Build TLS flash image */ + gsize flash_len; + g_autofree guint8 *flash_data = + validity_pair_build_tls_flash (ps, &flash_len); + + /* CMD 0x41: WRITE_FLASH + * Format: [0x41][partition:1][flag:1][reserved:2][offset:4LE][size:4LE][data] + * python-validity: pack('cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("write_flash failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +pair_write_clean_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK && + self->cmd_response_status != 0x0491) + fp_warn ("post-write cleanups status=0x%04x", + self->cmd_response_status); + fpi_ssm_next_state (ssm); + + return; +} + +/* ---- Phase 5: Reboot ---- */ +static void +pair_reboot_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + ValidityPairState *ps = &self->pair_state; + + /* Reboot: 0x05 0x02 0x00 (python-validity: tls.cmd(unhex('050200'))) + * Use raw USB — TLS may not be established (factory reset path). */ + guint8 cmd[] = { VCSFW_CMD_REBOOT, 0x02, 0x00 }; + + ps->reboot_pending = TRUE; + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_get_flash_info (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* CMD 0x3e: GET_FLASH_INFO — ask how many partitions exist */ + guint8 cmd[] = { VCSFW_CMD_GET_FLASH_INFO }; + + vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_erase_clean_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* CMD 0x1a: call_cleanups after erase */ + guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +pair_write_clean_send (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + void validity_pair_run_state (FpiSsm *ssm, FpDevice *dev) { FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - ValidityPairState *ps = &self->pair_state; switch (fpi_ssm_get_cur_state (ssm)) { - /* ---- Phase 1: Pre-TLS (raw USB) ---- */ - case PAIR_GET_FLASH_INFO: - { - /* CMD 0x3e: GET_FLASH_INFO — ask how many partitions exist */ - guint8 cmd[] = { VCSFW_CMD_GET_FLASH_INFO }; - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_get_flash_info (ssm, self); break; case PAIR_GET_FLASH_INFO_RECV: @@ -641,518 +1370,69 @@ validity_pair_run_state (FpiSsm *ssm, break; case PAIR_CHECK_NEEDED: - { - /* Parse CMD 0x3e response: status(2) + data */ - if (!self->cmd_response_data || - self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("GET_FLASH_INFO failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - if (!validity_pair_parse_flash_info (self->cmd_response_data, - self->cmd_response_len, - &ps->flash_ic, - &ps->num_partitions)) - { - fp_warn ("Failed to parse flash info"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - if (ps->num_partitions > 0) - { - fp_info ("Flash has %u partitions — verifying TLS keys", - ps->num_partitions); - /* Read flash partition 1 to check if TLS keys exist. - * If they do, pairing is complete. If not, we re-pair. */ - fpi_ssm_next_state (ssm); - return; - } - - fp_info ("Flash has 0 partitions — device needs pairing"); - - /* Look up device descriptor */ - ps->dev_desc = validity_hal_device_lookup (self->dev_type); - if (!ps->dev_desc) - { - fp_warn ("No HAL descriptor for dev_type=%u", self->dev_type); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); - return; - } - - /* No partitions — skip TLS verify, go straight to pairing */ - fpi_ssm_jump_to_state (ssm, PAIR_SEND_RESET_BLOB); - } + pair_check_needed (ssm, self); break; case PAIR_VERIFY_TLS_SEND: - { - /* Read flash partition 1 (TLS cert store) to verify keys exist */ - guint8 cmd[13]; - cmd[0] = VCSFW_CMD_READ_FLASH; - cmd[1] = 0x01; /* partition */ - cmd[2] = 0x01; /* access flag */ - FP_WRITE_UINT16_LE (&cmd[3], 0x0000); - FP_WRITE_UINT32_LE (&cmd[5], 0x0000); - FP_WRITE_UINT32_LE (&cmd[9], 0x1000); - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_verify_tls_send (ssm, self); break; case PAIR_VERIFY_TLS_RECV: - { - /* Check if TLS flash has valid key data */ - gboolean have_keys = FALSE; - - if (self->cmd_response_status == VCSFW_STATUS_OK && - self->cmd_response_data && self->cmd_response_len > 6) - { - guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data); - const guint8 *flash_data = self->cmd_response_data + 6; - gsize flash_avail = self->cmd_response_len - 6; - - if (flash_sz > flash_avail) - flash_sz = flash_avail; - - /* Quick check: scan for block IDs 3 (cert), 4 (privkey), 6 (ecdh) */ - const guint8 *pos = flash_data; - gsize remaining = flash_sz; - gboolean found_priv = FALSE, found_ecdh = FALSE, found_cert = FALSE; - - while (remaining >= 36) /* header(4) + hash(32) */ - { - guint16 block_id = FP_READ_UINT16_LE (pos); - guint16 block_size = FP_READ_UINT16_LE (pos + 2); - - if (block_id == 0xFFFF) - break; - - pos += 36; /* skip header + hash */ - remaining -= 36; - - if (block_size > remaining) - break; - - if (block_id == 4) - found_priv = TRUE; - if (block_id == 6) - found_ecdh = TRUE; - if (block_id == 3) - found_cert = TRUE; - - pos += block_size; - remaining -= block_size; - } - - have_keys = found_priv && found_ecdh && found_cert; - } - - if (have_keys) - { - fp_info ("TLS keys verified on flash — pairing not needed"); - fpi_ssm_jump_to_state (ssm, PAIR_DONE); - return; - } - - fp_info ("TLS keys missing from flash — starting pairing"); - - /* Look up device descriptor */ - ps->dev_desc = validity_hal_device_lookup (self->dev_type); - if (!ps->dev_desc) - { - fp_warn ("No HAL descriptor for dev_type=%u", self->dev_type); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); - return; - } - - fpi_ssm_next_state (ssm); - } + pair_verify_tls_recv (ssm, self); break; case PAIR_SEND_RESET_BLOB: - { - /* Send reset_blob via raw USB (python-validity: usb.cmd(reset_blob)) */ - if (!ps->dev_desc->reset_blob || ps->dev_desc->reset_blob_len == 0) - { - fp_warn ("No reset_blob available for this device"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED)); - return; - } - - vcsfw_cmd_send (self, ssm, - ps->dev_desc->reset_blob, - ps->dev_desc->reset_blob_len, NULL); - } + pair_send_reset_blob (ssm, self); break; case PAIR_SEND_RESET_BLOB_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("reset_blob failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fpi_ssm_next_state (ssm); - } + pair_send_reset_blob_recv (ssm, self); break; case PAIR_GENERATE_KEYS: - { - /* Generate ECDH P-256 key pair */ - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_EC, NULL); - EVP_PKEY_keygen_init (pctx); - EVP_PKEY_CTX_set_ec_paramgen_curve_nid (pctx, NID_X9_62_prime256v1); - EVP_PKEY_keygen (pctx, &ps->client_key); - EVP_PKEY_CTX_free (pctx); - - if (!ps->client_key) - { - fp_warn ("ECDH key generation failed"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - fp_info ("Generated ECDH client key pair"); - fpi_ssm_next_state (ssm); - } + pair_generate_keys (ssm, self); break; case PAIR_PARTITION_FLASH_SEND: - { - /* Extract public key coordinates (little-endian) */ - BIGNUM *pub_x_bn = NULL, *pub_y_bn = NULL; - EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x_bn); - EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y_bn); - - guint8 pub_x_be[32], pub_y_be[32]; - guint8 pub_x_le[32], pub_y_le[32]; - - BN_bn2binpad (pub_x_bn, pub_x_be, 32); - BN_bn2binpad (pub_y_bn, pub_y_be, 32); - BN_free (pub_x_bn); - BN_free (pub_y_bn); - - /* Convert big-endian → little-endian (python-validity uses LE) */ - for (int i = 0; i < 32; i++) - { - pub_x_le[i] = pub_x_be[31 - i]; - pub_y_le[i] = pub_y_be[31 - i]; - } - - /* Build CMD 0x4f */ - gsize cmd_len; - g_autofree guint8 *cmd = validity_pair_build_partition_flash_cmd ( - &ps->flash_ic, - ps->dev_desc->flash_layout, - pub_x_le, pub_y_le, - &cmd_len); - - if (!cmd) - { - fp_warn ("Failed to build partition_flash command"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - fp_info ("Sending partition_flash (CMD 0x4f): %" G_GSIZE_FORMAT " bytes", - cmd_len); - - /* NOTE: partition_flash is sent via raw USB (TLS not yet active). - * python-validity sends it through tls.cmd() which falls back to - * raw USB when secure_rx/secure_tx are false. */ - vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL); - } + pair_partition_flash_send (ssm, self); break; case PAIR_PARTITION_FLASH_RECV: - { - if (self->cmd_response_status == 0x0404) - { - /* 0x0404 = partitions already exist (half-initialized device). - * Factory reset will wipe flash, then reboot. Next device open - * will start with a clean slate and full pairing will succeed. */ - fp_info ("Flash already partitioned (0x0404) — factory reset needed"); - fpi_ssm_next_state (ssm); - return; - } - - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("partition_flash failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Response: [cert_len:4LE][cert_data:cert_len][...] */ - if (self->cmd_response_data && self->cmd_response_len >= 4) - { - guint32 cert_len = FP_READ_UINT32_LE (self->cmd_response_data); - if (cert_len <= self->cmd_response_len - 4) - { - ps->server_cert = g_memdup2 (self->cmd_response_data + 4, - cert_len); - ps->server_cert_len = cert_len; - fp_info ("Received server certificate: %u bytes", cert_len); - } - } - - /* Skip factory reset states — go straight to CMD50 */ - fpi_ssm_jump_to_state (ssm, PAIR_CMD50_SEND); - } + pair_partition_flash_recv (ssm, self); break; case PAIR_FACTORY_RESET_SEND: - { - /* CMD 0x10 + 0x61 zero bytes: wipes flash partition table. - * python-validity: usb.cmd(b'\x10' + b'\0' * 0x61) */ - guint8 cmd[98]; - memset (cmd, 0, sizeof (cmd)); - cmd[0] = 0x10; - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_factory_reset_send (ssm, self); break; case PAIR_FACTORY_RESET_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("Factory reset cmd 0x10 status=0x%04x", - self->cmd_response_status); - else - fp_info ("Factory reset complete — rebooting sensor"); - - /* Reboot; next device open will pair from clean state */ - fpi_ssm_jump_to_state (ssm, PAIR_REBOOT_SEND); - } + pair_factory_reset_recv (ssm, self); break; case PAIR_CMD50_SEND: - { - /* CMD 0x50: get ECDH server parameters - * python-validity: usb.cmd(unhex('50')) */ - guint8 cmd[] = { VCSFW_CMD_GET_ECDH }; - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_cmd50_send (ssm, self); break; case PAIR_CMD50_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("CMD 0x50 failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - fpi_ssm_next_state (ssm); - } + pair_cmd50_recv (ssm, self); break; case PAIR_CMD50_PROCESS: - { - /* Response: [length:4LE][zeros:...][ecdh_blob:400] - * python-validity: - * l, = unpack('cmd_response_data || self->cmd_response_len < 404) - { - fp_warn ("CMD 0x50 response too short: %" G_GSIZE_FORMAT, - self->cmd_response_len); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - guint32 resp_len = FP_READ_UINT32_LE (self->cmd_response_data); - const guint8 *ecdh_data = self->cmd_response_data + - self->cmd_response_len - 400; - - fp_info ("CMD 0x50 response: declared_len=%u, actual=%" G_GSIZE_FORMAT, - resp_len, self->cmd_response_len); - - /* Store ECDH blob: handle_ecdh stores raw blob, extracts pubkey. - * We store it for TLS flash persistence and set up tls.ecdh_q - * via the existing validity_tls code path. */ - ps->ecdh_blob = g_memdup2 (ecdh_data, 400); - ps->ecdh_blob_len = 400; - - /* Parse ECDH blob to extract server public key. - * This sets self->tls.ecdh_q and ecdh_blob. */ - self->tls.ecdh_blob = g_memdup2 (ecdh_data, 400); - self->tls.ecdh_blob_len = 400; - - /* Extract X,Y coordinates from ECDH blob for ecdh_q. - * Format: [header:8][x:32 LE][padding:36][y:32 LE][...] */ - const guint8 *x_le = ecdh_data + TLS_ECDH_X_OFFSET; - const guint8 *y_le = ecdh_data + TLS_ECDH_Y_OFFSET; - - guint8 x_be[32], y_be[32]; - for (int i = 0; i < 32; i++) - { - x_be[i] = x_le[31 - i]; - y_be[i] = y_le[31 - i]; - } - - /* Build ECDH server public key */ - OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new (); - OSSL_PARAM_BLD_push_utf8_string (bld, OSSL_PKEY_PARAM_GROUP_NAME, - "prime256v1", 0); - guint8 pub_uncompressed[65]; - pub_uncompressed[0] = 0x04; - memcpy (pub_uncompressed + 1, x_be, 32); - memcpy (pub_uncompressed + 33, y_be, 32); - OSSL_PARAM_BLD_push_octet_string (bld, OSSL_PKEY_PARAM_PUB_KEY, - pub_uncompressed, 65); - - OSSL_PARAM *params = OSSL_PARAM_BLD_to_param (bld); - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL); - EVP_PKEY_fromdata_init (ctx); - EVP_PKEY_fromdata (ctx, &self->tls.ecdh_q, EVP_PKEY_PUBLIC_KEY, params); - EVP_PKEY_CTX_free (ctx); - OSSL_PARAM_free (params); - OSSL_PARAM_BLD_free (bld); - - if (!self->tls.ecdh_q) - { - fp_warn ("Failed to build ECDH server public key"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - /* Encrypt client private key → priv_blob (handle_priv) - * python-validity: tls.handle_priv(encrypt_key(client_private, client_public)) */ - - /* First, derive PSK if not already done */ - validity_tls_derive_psk (&self->tls); - - /* Extract private key scalar (little-endian) */ - BIGNUM *priv_bn = NULL; - EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_PRIV_KEY, &priv_bn); - - guint8 priv_be[32], priv_le[32]; - BN_bn2binpad (priv_bn, priv_be, 32); - BN_free (priv_bn); - - for (int i = 0; i < 32; i++) - priv_le[i] = priv_be[31 - i]; - - /* Get public key LE coords */ - BIGNUM *pub_x_bn = NULL, *pub_y_bn = NULL; - EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_X, &pub_x_bn); - EVP_PKEY_get_bn_param (ps->client_key, OSSL_PKEY_PARAM_EC_PUB_Y, &pub_y_bn); - - guint8 pub_x_be2[32], pub_y_be2[32]; - guint8 pub_x_le2[32], pub_y_le2[32]; - BN_bn2binpad (pub_x_bn, pub_x_be2, 32); - BN_bn2binpad (pub_y_bn, pub_y_be2, 32); - BN_free (pub_x_bn); - BN_free (pub_y_bn); - - for (int i = 0; i < 32; i++) - { - pub_x_le2[i] = pub_x_be2[31 - i]; - pub_y_le2[i] = pub_y_be2[31 - i]; - } - - gsize priv_blob_len; - ps->priv_blob = validity_pair_encrypt_key (priv_le, pub_x_le2, pub_y_le2, - self->tls.psk_encryption_key, - self->tls.psk_validation_key, - &priv_blob_len); - ps->priv_blob_len = priv_blob_len; - - /* Also store in TLS state for handle_priv path */ - self->tls.priv_blob = g_memdup2 (ps->priv_blob, ps->priv_blob_len); - self->tls.priv_blob_len = ps->priv_blob_len; - - /* Store server cert in TLS state too */ - if (ps->server_cert) - { - self->tls.tls_cert = g_memdup2 (ps->server_cert, ps->server_cert_len); - self->tls.tls_cert_len = ps->server_cert_len; - } - - /* Set priv_key — the TLS handshake needs the actual EC private key - * (EVP_PKEY*) to sign cert_verify. We have it as ps->client_key. */ - if (self->tls.priv_key) - EVP_PKEY_free (self->tls.priv_key); - self->tls.priv_key = EVP_PKEY_dup (ps->client_key); - - OPENSSL_cleanse (priv_le, sizeof (priv_le)); - OPENSSL_cleanse (priv_be, sizeof (priv_be)); - - fp_info ("ECDH exchange complete, private key encrypted"); - fpi_ssm_next_state (ssm); - } + pair_cmd50_process (ssm, self); break; case PAIR_CLEANUPS_SEND: - { - /* CMD 0x1a: call_cleanups after CMD 0x50 - * python-validity: call_cleanups() in finally block */ - guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_cleanups_send (ssm, self); break; case PAIR_CLEANUPS_RECV: - { - /* Ignore "nothing to commit" (0x0491) status */ - if (self->cmd_response_status != VCSFW_STATUS_OK && - self->cmd_response_status != 0x0491) - fp_warn ("cleanups failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } - break; - - /* ---- Phase 2: TLS handshake ---- */ + pair_cleanups_recv (ssm, self); case PAIR_TLS_HANDSHAKE: - { - /* Establish TLS session — python-validity: tls.open() - * Uses subsm: tls_ssm completion/failure propagates to pair SSM. - * NOTE: do NOT overwrite self->open_ssm here — it must remain - * pointing to the open SSM for pair_ssm_done to work. */ - FpiSsm *tls_ssm = fpi_ssm_new (dev, - validity_tls_handshake_run_state, - TLS_HS_NUM_STATES); - fpi_ssm_start_subsm (ssm, tls_ssm); - } - break; - - /* ---- Phase 3: Flash erase (TLS-wrapped) ---- */ + pair_tls_handshake (ssm, self); case PAIR_ERASE_DBE_SEND: - { - /* Send db_write_enable before each erase - * python-validity: erase_flash() → tls.cmd(db_write_enable) */ - fp_info ("Erasing partition %u (step %u/%u)", - pair_erase_partition_ids[ps->erase_step], - ps->erase_step + 1, - (guint) VALIDITY_PAIR_NUM_ERASE_STEPS); - - vcsfw_tls_cmd_send (self, ssm, - ps->dev_desc->db_write_enable, - ps->dev_desc->db_write_enable_len, NULL); - } + pair_erase_dbe_send (ssm, self); break; case PAIR_ERASE_DBE_RECV: @@ -1161,65 +1441,26 @@ validity_pair_run_state (FpiSsm *ssm, break; case PAIR_ERASE_SEND: - { - /* CMD 0x3f: erase partition */ - guint8 cmd[2]; - cmd[0] = VCSFW_CMD_ERASE_FLASH; - cmd[1] = pair_erase_partition_ids[ps->erase_step]; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_erase_send (ssm, self); break; case PAIR_ERASE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("erase partition %u failed: status=0x%04x", - pair_erase_partition_ids[ps->erase_step], - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + pair_erase_recv (ssm, self); break; case PAIR_ERASE_CLEAN_SEND: - { - /* CMD 0x1a: call_cleanups after erase */ - guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_erase_clean_send (ssm, self); break; case PAIR_ERASE_CLEAN_RECV: - { - /* Ignore "nothing to commit" */ - if (self->cmd_response_status != VCSFW_STATUS_OK && - self->cmd_response_status != 0x0491) - fp_warn ("post-erase cleanups status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } + pair_erase_clean_recv (ssm, self); break; case PAIR_ERASE_LOOP: - { - ps->erase_step++; - if (ps->erase_step < VALIDITY_PAIR_NUM_ERASE_STEPS) - { - fpi_ssm_jump_to_state (ssm, PAIR_ERASE_DBE_SEND); - return; - } - fpi_ssm_next_state (ssm); - } - break; - - /* ---- Phase 4: Write TLS flash (TLS-wrapped) ---- */ + pair_erase_loop (ssm, self); case PAIR_WRITE_DBE_SEND: - { - /* db_write_enable before write_flash */ - vcsfw_tls_cmd_send (self, ssm, - ps->dev_desc->db_write_enable, - ps->dev_desc->db_write_enable_len, NULL); - } + pair_write_dbe_send (ssm, self); break; case PAIR_WRITE_DBE_RECV: @@ -1227,80 +1468,27 @@ validity_pair_run_state (FpiSsm *ssm, break; case PAIR_WRITE_FLASH_SEND: - { - /* Build TLS flash image */ - gsize flash_len; - g_autofree guint8 *flash_data = - validity_pair_build_tls_flash (ps, &flash_len); - - /* CMD 0x41: WRITE_FLASH - * Format: [0x41][partition:1][flag:1][reserved:2][offset:4LE][size:4LE][data] - * python-validity: pack('cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("write_flash failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fpi_ssm_next_state (ssm); - } + pair_write_flash_recv (ssm, self); break; case PAIR_WRITE_CLEAN_SEND: - { - guint8 cmd[] = { VCSFW_CMD_CLEANUPS }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_write_clean_send (ssm, self); break; case PAIR_WRITE_CLEAN_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK && - self->cmd_response_status != 0x0491) - fp_warn ("post-write cleanups status=0x%04x", - self->cmd_response_status); - fpi_ssm_next_state (ssm); - } - break; - - /* ---- Phase 5: Reboot ---- */ + pair_write_clean_recv (ssm, self); case PAIR_REBOOT_SEND: - { - /* Reboot: 0x05 0x02 0x00 (python-validity: tls.cmd(unhex('050200'))) - * Use raw USB — TLS may not be established (factory reset path). */ - guint8 cmd[] = { VCSFW_CMD_REBOOT, 0x02, 0x00 }; - ps->reboot_pending = TRUE; - vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + pair_reboot_send (ssm, self); break; case PAIR_REBOOT_RECV: - { - fp_info ("Reboot command sent — device will re-enumerate"); - fpi_ssm_next_state (ssm); - } + fp_info ("Reboot command sent — device will re-enumerate"); + fpi_ssm_next_state (ssm); break; case PAIR_DONE: diff --git a/libfprint/drivers/validity/validity_tls.c b/libfprint/drivers/validity/validity_tls.c index b6e7c8c5..f74b0768 100644 --- a/libfprint/drivers/validity/validity_tls.c +++ b/libfprint/drivers/validity/validity_tls.c @@ -1725,6 +1725,23 @@ validity_tls_parse_server_finish (ValidityTlsState *tls, * TLS Flash Read SSM (reads flash partition 1 over USB) * ================================================================ */ +static void +tls_flash_read_cmd (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* READ_FLASH(partition=1, offset=0, size=0x1000) + * Format from python-validity: pack('ssm); } +static void +tls_hs_send_client_hello (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + gsize cmd_len; + guint8 *cmd = validity_tls_build_client_hello (&self->tls, &cmd_len); + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, cmd_len); + memcpy (transfer->buffer, cmd, cmd_len); + g_free (cmd); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +tls_hs_recv_server_hello (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + /* Receive raw TLS records (ServerHello + CertReq + ServerHelloDone) */ + 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_raw_recv_cb, NULL); +} + +static void +tls_hs_send_client_finish (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + /* Parse the stored ServerHello response (synchronous) */ + GError *error = NULL; + + if (!self->cmd_response_data) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "TLS handshake: no ServerHello response")); + return; + } + + if (!validity_tls_parse_server_hello (&self->tls, + self->cmd_response_data, + self->cmd_response_len, + &error)) + { + fpi_ssm_mark_failed (ssm, error); + return; + } + + /* Build and send Certificate + KeyExchange + CertVerify + CCS + Finished */ + gsize cmd_len; + guint8 *cmd = validity_tls_build_client_finish (&self->tls, &cmd_len); + + transfer = fpi_usb_transfer_new (dev); + transfer->short_is_error = TRUE; + transfer->ssm = ssm; + fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, cmd_len); + memcpy (transfer->buffer, cmd, cmd_len); + g_free (cmd); + fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, + fpi_ssm_usb_transfer_cb, NULL); +} + +static void +tls_hs_recv_server_finish (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + FpiUsbTransfer *transfer; + + /* Receive raw TLS records (CCS + encrypted Finished) */ + 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_raw_recv_cb, NULL); +} + +static void +tls_hs_parse_server_finish (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + GError *error = NULL; + + if (!self->cmd_response_data) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "TLS handshake: no ServerFinish response")); + return; + } + + if (!validity_tls_parse_server_finish (&self->tls, + self->cmd_response_data, + self->cmd_response_len, + &error)) + { + fpi_ssm_mark_failed (ssm, error); + return; + } + + fp_info ("TLS session established (secure_rx=%d secure_tx=%d)", + self->tls.secure_rx, self->tls.secure_tx); + fpi_ssm_mark_completed (ssm); +} + void validity_tls_handshake_run_state (FpiSsm *ssm, FpDevice *dev) { FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - FpiUsbTransfer *transfer; switch (fpi_ssm_get_cur_state (ssm)) { case TLS_HS_SEND_CLIENT_HELLO: - { - gsize cmd_len; - guint8 *cmd = validity_tls_build_client_hello (&self->tls, &cmd_len); - - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, cmd_len); - memcpy (transfer->buffer, cmd, cmd_len); - g_free (cmd); - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); - } + tls_hs_send_client_hello (ssm, self); break; case TLS_HS_RECV_SERVER_HELLO: - /* Receive raw TLS records (ServerHello + CertReq + ServerHelloDone) */ - 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_raw_recv_cb, NULL); + tls_hs_recv_server_hello (ssm, self); break; case TLS_HS_SEND_CLIENT_FINISH: - { - /* Parse the stored ServerHello response (synchronous) */ - GError *error = NULL; - if (!self->cmd_response_data) - { - fpi_ssm_mark_failed (ssm, - fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "TLS handshake: no ServerHello response")); - return; - } - - if (!validity_tls_parse_server_hello (&self->tls, - self->cmd_response_data, - self->cmd_response_len, - &error)) - { - fpi_ssm_mark_failed (ssm, error); - return; - } - - /* Build and send Certificate + KeyExchange + CertVerify + CCS + Finished */ - gsize cmd_len; - guint8 *cmd = validity_tls_build_client_finish (&self->tls, &cmd_len); - - transfer = fpi_usb_transfer_new (dev); - transfer->short_is_error = TRUE; - transfer->ssm = ssm; - fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, cmd_len); - memcpy (transfer->buffer, cmd, cmd_len); - g_free (cmd); - fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL, - fpi_ssm_usb_transfer_cb, NULL); - } + tls_hs_send_client_finish (ssm, self); break; case TLS_HS_RECV_SERVER_FINISH: - /* Receive raw TLS records (CCS + encrypted Finished) */ - 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_raw_recv_cb, NULL); + tls_hs_recv_server_finish (ssm, self); break; case TLS_HS_PARSE_SERVER_FINISH: - { - GError *error = NULL; - if (!self->cmd_response_data) - { - fpi_ssm_mark_failed (ssm, - fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, - "TLS handshake: no ServerFinish response")); - return; - } - - if (!validity_tls_parse_server_finish (&self->tls, - self->cmd_response_data, - self->cmd_response_len, - &error)) - { - fpi_ssm_mark_failed (ssm, error); - return; - } - - fp_info ("TLS session established (secure_rx=%d secure_tx=%d)", - self->tls.secure_rx, self->tls.secure_tx); - fpi_ssm_mark_completed (ssm); - } + tls_hs_parse_server_finish (ssm, self); break; + } } diff --git a/libfprint/drivers/validity/validity_verify.c b/libfprint/drivers/validity/validity_verify.c index 6a9f3712..54558ad2 100644 --- a/libfprint/drivers/validity/validity_verify.c +++ b/libfprint/drivers/validity/validity_verify.c @@ -317,6 +317,158 @@ validity_find_gallery_match (GPtrArray *gallery, * Verify/Identify SSM * ================================================================ */ +static void +verify_build_capture (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_capture_build_cmd_02 (&self->capture, + self->sensor.type_info, + VALIDITY_CAPTURE_IDENTIFY, + &cmd_len); + + if (!cmd) + { + fp_warn ("Failed to build identify capture command"); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); + return; + } + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +verify_capture_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("Capture (identify) failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +verify_match_start (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x5E: match_finger */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_match_finger (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +verify_match_start_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("match_finger failed: status=0x%04x", + self->cmd_response_status); + /* No match — continue to cleanup */ + fpi_ssm_jump_to_state (ssm, VERIFY_CLEANUP); + return; + } + fpi_ssm_next_state (ssm); +} + +static void +verify_get_result (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x60: get_match_result */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_match_result (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +verify_get_result_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_info ("No match found (status=0x%04x)", + self->cmd_response_status); + /* Store no-match indicator */ + g_clear_pointer (&self->bulk_data, g_free); + self->bulk_data_len = 0; + } + else if (self->cmd_response_data && self->cmd_response_len > 0) + { + /* Store match result for later reporting */ + g_clear_pointer (&self->bulk_data, g_free); + self->bulk_data = g_memdup2 (self->cmd_response_data, + self->cmd_response_len); + self->bulk_data_len = self->cmd_response_len; + } + + fpi_ssm_next_state (ssm); +} + +static void +verify_cleanup (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x62: match_cleanup */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_match_cleanup (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +verify_led_on (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + const guint8 *cmd = validity_capture_glow_start_cmd (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); +} + +static void +verify_get_prg_status (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */ + const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +verify_capture_stop (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* cmd 0x04: capture stop/cleanup */ + const guint8 cmd[] = { 0x04 }; + + vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); +} + +static void +verify_led_off (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + const guint8 *cmd = validity_capture_glow_end_cmd (&cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); +} + static void verify_run_state (FpiSsm *ssm, FpDevice *dev) @@ -326,11 +478,7 @@ verify_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case VERIFY_LED_ON: - { - gsize cmd_len; - const guint8 *cmd = validity_capture_glow_start_cmd (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - } + verify_led_on (ssm, self); break; case VERIFY_LED_ON_RECV: @@ -338,23 +486,7 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_BUILD_CAPTURE: - { - gsize cmd_len; - guint8 *cmd = validity_capture_build_cmd_02 (&self->capture, - self->sensor.type_info, - VALIDITY_CAPTURE_IDENTIFY, - &cmd_len); - if (!cmd) - { - fp_warn ("Failed to build identify capture command"); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_GENERAL)); - return; - } - - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + verify_build_capture (ssm, self); break; case VERIFY_CAPTURE_SEND: @@ -362,17 +494,7 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_CAPTURE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("Capture (identify) failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - fpi_ssm_next_state (ssm); - } + verify_capture_recv (ssm, self); break; case VERIFY_WAIT_FINGER: @@ -384,11 +506,7 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_GET_PRG_STATUS: - { - /* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */ - const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + verify_get_prg_status (ssm, self); break; case VERIFY_GET_PRG_STATUS_RECV: @@ -397,11 +515,7 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_CAPTURE_STOP: - { - /* cmd 0x04: capture stop/cleanup */ - const guint8 cmd[] = { 0x04 }; - vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL); - } + verify_capture_stop (ssm, self); break; case VERIFY_CAPTURE_STOP_RECV: @@ -410,27 +524,11 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_MATCH_START: - { - /* cmd 0x5E: match_finger */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_match_finger (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + verify_match_start (ssm, self); break; case VERIFY_MATCH_START_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("match_finger failed: status=0x%04x", - self->cmd_response_status); - /* No match — continue to cleanup */ - fpi_ssm_jump_to_state (ssm, VERIFY_CLEANUP); - return; - } - fpi_ssm_next_state (ssm); - } + verify_match_start_recv (ssm, self); break; case VERIFY_WAIT_MATCH_INT: @@ -439,46 +537,15 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_GET_RESULT: - { - /* cmd 0x60: get_match_result */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_match_result (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + verify_get_result (ssm, self); break; case VERIFY_GET_RESULT_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_info ("No match found (status=0x%04x)", - self->cmd_response_status); - /* Store no-match indicator */ - g_clear_pointer (&self->bulk_data, g_free); - self->bulk_data_len = 0; - } - else if (self->cmd_response_data && self->cmd_response_len > 0) - { - /* Store match result for later reporting */ - g_clear_pointer (&self->bulk_data, g_free); - self->bulk_data = g_memdup2 (self->cmd_response_data, - self->cmd_response_len); - self->bulk_data_len = self->cmd_response_len; - } - - fpi_ssm_next_state (ssm); - } + verify_get_result_recv (ssm, self); break; case VERIFY_CLEANUP: - { - /* cmd 0x62: match_cleanup */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_match_cleanup (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + verify_cleanup (ssm, self); break; case VERIFY_CLEANUP_RECV: @@ -487,11 +554,7 @@ verify_run_state (FpiSsm *ssm, break; case VERIFY_LED_OFF: - { - gsize cmd_len; - const guint8 *cmd = validity_capture_glow_end_cmd (&cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - } + verify_led_off (ssm, self); break; case VERIFY_LED_OFF_RECV: @@ -612,115 +675,138 @@ validity_identify (FpDevice *device) * List prints — enumerate enrolled fingerprints from sensor DB * ================================================================ */ +static void +list_get_storage (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user_storage ( + VALIDITY_STORAGE_NAME, &cmd_len); + + self->list_user_idx = 0; + memset (&self->list_storage, 0, sizeof (self->list_storage)); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +list_get_storage_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_info ("No user storage found (status=0x%04x)", + self->cmd_response_status); + fpi_ssm_jump_to_state (ssm, LIST_DONE); + return; + } + + if (!self->cmd_response_data || + !validity_db_parse_user_storage (self->cmd_response_data, + self->cmd_response_len, + &self->list_storage)) + { + fp_info ("Failed to parse user storage — no enrolled prints"); + fpi_ssm_jump_to_state (ssm, LIST_DONE); + return; + } + + fp_info ("Storage '%s': %u users", + self->list_storage.name ? self->list_storage.name : "", + self->list_storage.user_count); + + if (self->list_storage.user_count == 0) + { + fpi_ssm_jump_to_state (ssm, LIST_DONE); + return; + } + + self->list_user_idx = 0; + fpi_ssm_next_state (ssm); +} + +static void +list_get_user (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->list_user_idx >= self->list_storage.user_count) + { + fpi_ssm_jump_to_state (ssm, LIST_DONE); + return; + } + + guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; + + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +list_get_user_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + GPtrArray *prints_array = fpi_ssm_get_data (ssm); + + if (self->cmd_response_status == VCSFW_STATUS_OK && + self->cmd_response_data) + { + ValidityUser user = { 0 }; + + if (validity_db_parse_user (self->cmd_response_data, + self->cmd_response_len, + &user)) + { + for (guint16 i = 0; i < user.finger_count; i++) + { + FpPrint *print = fp_print_new (dev); + gint finger = validity_subtype_to_finger ( + user.fingers[i].subtype); + + fpi_print_set_type (print, FPI_PRINT_RAW); + fpi_print_set_device_stored (print, TRUE); + if (finger >= 0) + fp_print_set_finger (print, (FpFinger) finger); + + g_ptr_array_add (prints_array, print); + } + + validity_user_clear (&user); + } + } + + self->list_user_idx++; + + if (self->list_user_idx < self->list_storage.user_count) + fpi_ssm_jump_to_state (ssm, LIST_GET_USER); + else + fpi_ssm_next_state (ssm); +} + static void list_run_state (FpiSsm *ssm, FpDevice *dev) { FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev); - GPtrArray *prints_array = fpi_ssm_get_data (ssm); switch (fpi_ssm_get_cur_state (ssm)) { case LIST_GET_STORAGE: - { - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user_storage ( - VALIDITY_STORAGE_NAME, &cmd_len); - self->list_user_idx = 0; - memset (&self->list_storage, 0, sizeof (self->list_storage)); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + list_get_storage (ssm, self); break; case LIST_GET_STORAGE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_info ("No user storage found (status=0x%04x)", - self->cmd_response_status); - fpi_ssm_jump_to_state (ssm, LIST_DONE); - return; - } - - if (!self->cmd_response_data || - !validity_db_parse_user_storage (self->cmd_response_data, - self->cmd_response_len, - &self->list_storage)) - { - fp_info ("Failed to parse user storage — no enrolled prints"); - fpi_ssm_jump_to_state (ssm, LIST_DONE); - return; - } - - fp_info ("Storage '%s': %u users", - self->list_storage.name ? self->list_storage.name : "", - self->list_storage.user_count); - - if (self->list_storage.user_count == 0) - { - fpi_ssm_jump_to_state (ssm, LIST_DONE); - return; - } - - self->list_user_idx = 0; - fpi_ssm_next_state (ssm); - } + list_get_storage_recv (ssm, self); break; case LIST_GET_USER: - { - if (self->list_user_idx >= self->list_storage.user_count) - { - fpi_ssm_jump_to_state (ssm, LIST_DONE); - return; - } - - guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; - - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + list_get_user (ssm, self); break; case LIST_GET_USER_RECV: - { - if (self->cmd_response_status == VCSFW_STATUS_OK && - self->cmd_response_data) - { - ValidityUser user = { 0 }; - - if (validity_db_parse_user (self->cmd_response_data, - self->cmd_response_len, - &user)) - { - for (guint16 i = 0; i < user.finger_count; i++) - { - FpPrint *print = fp_print_new (dev); - gint finger = validity_subtype_to_finger ( - user.fingers[i].subtype); - - fpi_print_set_type (print, FPI_PRINT_RAW); - fpi_print_set_device_stored (print, TRUE); - if (finger >= 0) - fp_print_set_finger (print, (FpFinger) finger); - - g_ptr_array_add (prints_array, print); - } - - validity_user_clear (&user); - } - } - - self->list_user_idx++; - - if (self->list_user_idx < self->list_storage.user_count) - fpi_ssm_jump_to_state (ssm, LIST_GET_USER); - else - fpi_ssm_next_state (ssm); - } + list_get_user_recv (ssm, self); break; case LIST_DONE: @@ -767,6 +853,149 @@ validity_list (FpDevice *device) * Delete print — remove a fingerprint record from the sensor DB * ================================================================ */ + +static void +delete_get_storage (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user_storage ( + VALIDITY_STORAGE_NAME, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +delete_get_storage_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + FpDevice *dev = FP_DEVICE (self); + + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + return; + } + + /* Parse into list_storage (shared with list SSM, not concurrent) */ + validity_user_storage_clear (&self->list_storage); + if (!self->cmd_response_data || + !validity_db_parse_user_storage (self->cmd_response_data, + self->cmd_response_len, + &self->list_storage)) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + return; + } + + self->delete_storage_dbid = self->list_storage.dbid; + + /* Extract finger subtype from the print to delete */ + { + FpPrint *print = NULL; + fpi_device_get_delete_data (dev, &print); + + FpFinger finger = fp_print_get_finger (print); + self->delete_finger_subtype = validity_finger_to_subtype (finger); + } + + self->list_user_idx = 0; + fpi_ssm_next_state (ssm); +} + +static void +delete_lookup_user (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Look up the user matching the print to delete. + * Iterate users to find one with a matching finger subtype. + * python-validity: db.lookup_user(identity) */ + if (self->list_user_idx >= self->list_storage.user_count) + { + /* No matching finger found across all users */ + fp_info ("Delete: no matching finger (subtype=%u) found in DB", + self->delete_finger_subtype); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); + return; + } + + { + guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); + } +} + +static void +delete_lookup_user_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Parse user and look for the finger to delete */ + if (self->cmd_response_status == VCSFW_STATUS_OK && + self->cmd_response_data) + { + ValidityUser user = { 0 }; + if (validity_db_parse_user (self->cmd_response_data, + self->cmd_response_len, + &user)) + { + for (guint16 i = 0; i < user.finger_count; i++) + { + if (user.fingers[i].subtype == self->delete_finger_subtype) + { + /* Found matching finger — store dbid for deletion */ + self->delete_finger_dbid = user.fingers[i].dbid; + validity_user_clear (&user); + fpi_ssm_next_state (ssm); + return; + } + } + validity_user_clear (&user); + } + } + + /* Try next user — jump back to DELETE_LOOKUP_USER */ + self->list_user_idx++; + fpi_ssm_jump_to_state (ssm, DELETE_LOOKUP_USER); +} + +static void +delete_del_record (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + /* Delete the finger record via cmd 0x48 + * python-validity: db.del_record(dbid) */ + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_del_record ( + self->delete_finger_dbid, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +delete_del_record_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + if (self->cmd_response_status != VCSFW_STATUS_OK) + { + fp_warn ("del_record failed: status=0x%04x", + self->cmd_response_status); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); + return; + } + + fp_info ("Deleted finger record: dbid=%u", self->delete_finger_dbid); + fpi_ssm_next_state (ssm); +} + static void delete_run_state (FpiSsm *ssm, FpDevice *dev) @@ -776,135 +1005,27 @@ delete_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case DELETE_GET_STORAGE: - { - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user_storage ( - VALIDITY_STORAGE_NAME, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + delete_get_storage (ssm, self); break; case DELETE_GET_STORAGE_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); - return; - } - - /* Parse into list_storage (shared with list SSM, not concurrent) */ - validity_user_storage_clear (&self->list_storage); - if (!self->cmd_response_data || - !validity_db_parse_user_storage (self->cmd_response_data, - self->cmd_response_len, - &self->list_storage)) - { - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); - return; - } - - self->delete_storage_dbid = self->list_storage.dbid; - - /* Extract finger subtype from the print to delete */ - { - FpPrint *print = NULL; - fpi_device_get_delete_data (dev, &print); - - FpFinger finger = fp_print_get_finger (print); - self->delete_finger_subtype = validity_finger_to_subtype (finger); - } - - self->list_user_idx = 0; - fpi_ssm_next_state (ssm); - } + delete_get_storage_recv (ssm, self); break; case DELETE_LOOKUP_USER: - { - /* Look up the user matching the print to delete. - * Iterate users to find one with a matching finger subtype. - * python-validity: db.lookup_user(identity) */ - if (self->list_user_idx >= self->list_storage.user_count) - { - /* No matching finger found across all users */ - fp_info ("Delete: no matching finger (subtype=%u) found in DB", - self->delete_finger_subtype); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND)); - return; - } - - { - guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user (user_dbid, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } - } + delete_lookup_user (ssm, self); break; case DELETE_LOOKUP_USER_RECV: - { - /* Parse user and look for the finger to delete */ - if (self->cmd_response_status == VCSFW_STATUS_OK && - self->cmd_response_data) - { - ValidityUser user = { 0 }; - if (validity_db_parse_user (self->cmd_response_data, - self->cmd_response_len, - &user)) - { - for (guint16 i = 0; i < user.finger_count; i++) - { - if (user.fingers[i].subtype == self->delete_finger_subtype) - { - /* Found matching finger — store dbid for deletion */ - self->delete_finger_dbid = user.fingers[i].dbid; - validity_user_clear (&user); - fpi_ssm_next_state (ssm); - return; - } - } - validity_user_clear (&user); - } - } - - /* Try next user — jump back to DELETE_LOOKUP_USER */ - self->list_user_idx++; - fpi_ssm_jump_to_state (ssm, DELETE_LOOKUP_USER); - } + delete_lookup_user_recv (ssm, self); break; case DELETE_DEL_RECORD: - { - /* Delete the finger record via cmd 0x48 - * python-validity: db.del_record(dbid) */ - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_del_record ( - self->delete_finger_dbid, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + delete_del_record (ssm, self); break; case DELETE_DEL_RECORD_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - { - fp_warn ("del_record failed: status=0x%04x", - self->cmd_response_status); - fpi_ssm_mark_failed (ssm, - fpi_device_error_new (FP_DEVICE_ERROR_PROTO)); - return; - } - - fp_info ("Deleted finger record: dbid=%u", self->delete_finger_dbid); - fpi_ssm_next_state (ssm); - } + delete_del_record_recv (ssm, self); break; case DELETE_DONE: @@ -940,6 +1061,74 @@ validity_delete (FpDevice *device) * python-validity: for user in db.get_user_storage(): db.del_record(user.dbid) * ================================================================ */ +static void +clear_get_storage (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_get_user_storage ( + VALIDITY_STORAGE_NAME, &cmd_len); + + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +clear_get_storage_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + + validity_user_storage_clear (&self->list_storage); + + if (self->cmd_response_status != VCSFW_STATUS_OK || + !self->cmd_response_data || + !validity_db_parse_user_storage (self->cmd_response_data, + self->cmd_response_len, + &self->list_storage)) + { + /* No storage or parse error — nothing to clear */ + fpi_ssm_jump_to_state (ssm, CLEAR_DONE); + return; + } + + self->list_user_idx = 0; + fpi_ssm_next_state (ssm); +} + +static void +clear_del_user (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + + if (self->list_user_idx >= self->list_storage.user_count) + { + fpi_ssm_jump_to_state (ssm, CLEAR_DONE); + return; + } + + guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; + + gsize cmd_len; + guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len); + vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); + g_free (cmd); +} + +static void +clear_del_user_recv (FpiSsm *ssm, + FpiDeviceValidity *self) +{ + + if (self->cmd_response_status != VCSFW_STATUS_OK) + fp_warn ("clear_storage: del_record(dbid=%u) failed: status=0x%04x", + self->list_storage.user_dbids[self->list_user_idx], + self->cmd_response_status); + + self->list_user_idx++; + fpi_ssm_jump_to_state (ssm, CLEAR_DEL_USER); +} + static void clear_run_state (FpiSsm *ssm, FpDevice *dev) @@ -949,62 +1138,19 @@ clear_run_state (FpiSsm *ssm, switch (fpi_ssm_get_cur_state (ssm)) { case CLEAR_GET_STORAGE: - { - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_get_user_storage ( - VALIDITY_STORAGE_NAME, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + clear_get_storage (ssm, self); break; case CLEAR_GET_STORAGE_RECV: - { - validity_user_storage_clear (&self->list_storage); - - if (self->cmd_response_status != VCSFW_STATUS_OK || - !self->cmd_response_data || - !validity_db_parse_user_storage (self->cmd_response_data, - self->cmd_response_len, - &self->list_storage)) - { - /* No storage or parse error — nothing to clear */ - fpi_ssm_jump_to_state (ssm, CLEAR_DONE); - return; - } - - self->list_user_idx = 0; - fpi_ssm_next_state (ssm); - } + clear_get_storage_recv (ssm, self); break; case CLEAR_DEL_USER: - { - if (self->list_user_idx >= self->list_storage.user_count) - { - fpi_ssm_jump_to_state (ssm, CLEAR_DONE); - return; - } - - guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx]; - - gsize cmd_len; - guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len); - vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL); - g_free (cmd); - } + clear_del_user (ssm, self); break; case CLEAR_DEL_USER_RECV: - { - if (self->cmd_response_status != VCSFW_STATUS_OK) - fp_warn ("clear_storage: del_record(dbid=%u) failed: status=0x%04x", - self->list_storage.user_dbids[self->list_user_idx], - self->cmd_response_status); - - self->list_user_idx++; - fpi_ssm_jump_to_state (ssm, CLEAR_DEL_USER); - } + clear_del_user_recv (ssm, self); break; case CLEAR_DONE: