From 56d44381b3b6c4b43c70b62cff4c9794b3551a22 Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Sun, 5 Apr 2026 23:40:41 +0200 Subject: [PATCH 1/6] Add goodix53x5 driver for Goodix 27c6:5395 (GF5288 HTSEC) Add a new FpImageDevice driver for the Goodix 53x5 family fingerprint sensor (USB ID 27c6:5395, silicon die GF5288 "HTSEC"). This is a raw-image-capture device using a proprietary GTLS encryption protocol, distinct from the existing goodixmoc Match-on-Chip driver. Protocol highlights: - Device presents as CDC ACM; both interfaces must be claimed to detach the cdc_acm kernel driver, followed by a USB reset - Every command receives an ACK (cat=0xB, cmd=0x0) before the reply - GTLS handshake: 3-step PSK-based using HMAC-SHA256 P_SHA256 PRF, derives AES-128 key/IV, HMAC key and per-session counters - Encrypted image: 8-byte header + 15 interleaved blocks (even=GEA stream cipher, odd=AES-128-CBC) + 32-byte HMAC-SHA256 trailer - GEA CRC uses a custom byte-pair-swap u32 encoding (not standard LE) - AES decryption uses NSS PK11 (same dependency as the uru4000 driver) - Sensor: 108x88 pixels, 12-bit depth, PSK = all-zero 32 bytes Initialization flow: ping -> firmware version -> reset -> chip ID -> OTP (calibration params) -> PSK hash check/write -> GTLS handshake -> config upload -> two calibration image captures (advancing the HMAC server counter) -> FDT cross-check -> sleep. Capture flow: EC power on -> FDT finger-down arm -> wait for finger -> manual FDT -> get image -> decrypt/decode -> report to libfprint. --- libfprint/drivers/goodix53x5/goodix53x5.c | 1817 +++++++++++++++++ libfprint/drivers/goodix53x5/goodix53x5.h | 168 ++ .../drivers/goodix53x5/goodix53x5_proto.c | 1084 ++++++++++ .../drivers/goodix53x5/goodix53x5_proto.h | 285 +++ libfprint/meson.build | 2 + meson.build | 2 + 6 files changed, 3358 insertions(+) create mode 100644 libfprint/drivers/goodix53x5/goodix53x5.c create mode 100644 libfprint/drivers/goodix53x5/goodix53x5.h create mode 100644 libfprint/drivers/goodix53x5/goodix53x5_proto.c create mode 100644 libfprint/drivers/goodix53x5/goodix53x5_proto.h diff --git a/libfprint/drivers/goodix53x5/goodix53x5.c b/libfprint/drivers/goodix53x5/goodix53x5.c new file mode 100644 index 00000000..688361c9 --- /dev/null +++ b/libfprint/drivers/goodix53x5/goodix53x5.c @@ -0,0 +1,1817 @@ +/* + * Goodix 53x5 series driver for libfprint + * Copyright (C) 2024 libfprint contributors + * + * Based on the reference Python implementation by goodix-fp-dump contributors + * https://github.com/nickel-org/goodix-fp-dump + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "goodix53x5" +#include "fpi-log.h" + +#include "goodix53x5.h" +#include "goodix53x5_proto.h" + +#include "drivers_api.h" + +#include + +#include +#include + +/* ============================================================ + * USB endpoint addresses (bulk) + * ============================================================ */ + +#define GOODIX53X5_EP_OUT (0x03 | FPI_USB_ENDPOINT_OUT) +#define GOODIX53X5_EP_IN (0x81 | FPI_USB_ENDPOINT_IN) + +/* USB transfer timeout (ms) */ +#define GOODIX53X5_TIMEOUT 5000 + +/* Maximum receive buffer size (big enough for the largest encrypted image) */ +#define GOODIX53X5_MAX_RECV (64 * 1024) + +/* ============================================================ + * GObject boilerplate + * ============================================================ */ + +G_DEFINE_TYPE (FpiDeviceGoodix53x5, fpi_device_goodix53x5, FP_TYPE_IMAGE_DEVICE) + +/* ============================================================ + * Forward declarations + * ============================================================ */ + +static void goodix53x5_start_capture_ssm (FpImageDevice *dev); + +/* ============================================================ + * Helpers: send and receive raw USB messages + * ============================================================ */ + +/* Forward declaration for ack_recv_cb */ +static void ack_recv_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error); + +/* + * Callback: called after a bulk-OUT transfer carrying an encoded message has + * completed. On success, posts an IN transfer to consume the device ACK. + * The expected ACK command_byte is stored in user_data (cast to GINT_TO_POINTER). + */ +static void +msg_send_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + FpiUsbTransfer *ack_transfer; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + /* Reset recv buffer and post an IN transfer to read the ACK */ + g_byte_array_set_size (self->recv_buf, 0); + + ack_transfer = fpi_usb_transfer_new (dev); + ack_transfer->ssm = transfer->ssm; + fpi_usb_transfer_fill_bulk (ack_transfer, + self->ep_in, + GOODIX53X5_USB_CHUNK_SIZE); + fpi_usb_transfer_submit (ack_transfer, + GOODIX53X5_TIMEOUT, + fpi_device_get_cancellable (dev), + ack_recv_cb, + user_data); /* command_byte expected in ACK */ +} + +/* + * Callback: accumulates ACK chunks and validates the ACK message. + * Once complete, advances the SSM. + */ +static void +ack_recv_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + guint8 expected_cmd_byte = (guint8) GPOINTER_TO_INT (user_data); + gsize chunk_len; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + chunk_len = (gsize) transfer->actual_length; + if (chunk_len == 0) + { + /* Empty read — re-submit */ + fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), + GOODIX53X5_TIMEOUT, + fpi_device_get_cancellable (dev), + ack_recv_cb, + user_data); + return; + } + + g_byte_array_append (self->recv_buf, transfer->buffer, (guint) chunk_len); + + /* Check if we have a complete message */ + if (self->recv_buf->len >= 3) + { + guint16 message_size = GUINT16_FROM_LE (*(guint16 *) &self->recv_buf->data[1]); + gsize total_needed = 3 + (gsize) message_size; + + if (self->recv_buf->len >= total_needed) + { + /* Validate it's an ACK: cat=0xB, cmd=0x0 */ + guint8 cmd_byte = self->recv_buf->data[0]; + guint8 cat = cmd_byte >> 4; + guint8 cmd = (cmd_byte & 0xF) >> 1; + + if (cat != GOODIX53X5_CAT_ACK || cmd != 0x0) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: expected ACK (cat=0xB cmd=0), " + "got cat=0x%x cmd=0x%x", + cat, cmd)); + return; + } + + /* Validate ACK payload echoes the right command_byte */ + if (message_size >= 1 && self->recv_buf->data[3] != expected_cmd_byte) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: ACK echoes wrong " + "command byte 0x%02x (expected 0x%02x)", + self->recv_buf->data[3], + expected_cmd_byte)); + return; + } + + /* ACK is valid — advance SSM */ + fpi_ssm_next_state (transfer->ssm); + return; + } + } + + /* Need more data */ + if (self->recv_buf->len >= GOODIX53X5_MAX_RECV) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: ACK receive buffer overflow")); + return; + } + + fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), + GOODIX53X5_TIMEOUT, + fpi_device_get_cancellable (dev), + ack_recv_cb, + user_data); +} + +/* + * Send an encoded message over bulk-OUT. + * After the OUT completes, the ACK from the device is consumed automatically. + * The SSM is advanced only after the ACK is validated. + */ +static void +goodix53x5_send_msg (FpiDeviceGoodix53x5 *self, + guint8 category, + guint8 command, + const guint8 *payload, + gsize payload_len) +{ + FpiUsbTransfer *transfer; + guint8 *encoded; + gsize encoded_len; + guint8 command_byte; + + encoded = goodix53x5_encode_message (category, command, payload, payload_len, &encoded_len); + + /* command_byte is the first byte of the encoded message */ + command_byte = encoded[0]; + + transfer = fpi_usb_transfer_new (FP_DEVICE (self)); + transfer->ssm = self->ssm; + fpi_usb_transfer_fill_bulk_full (transfer, + self->ep_out, + encoded, + encoded_len, + g_free); + fpi_usb_transfer_submit (transfer, + GOODIX53X5_TIMEOUT, + fpi_device_get_cancellable (FP_DEVICE (self)), + msg_send_cb, + GINT_TO_POINTER ((gint) command_byte)); +} + +/* + * Callback: called when a bulk-IN transfer has completed. + * Accumulates chunks into self->recv_buf until we have a complete message, + * then advances the SSM. + * + * user_data carries the timeout (as GUINT_TO_POINTER) to use when + * re-submitting for more data. 0 means no timeout (unlimited wait). + */ +static void +msg_recv_cb (FpiUsbTransfer *transfer, + FpDevice *dev, + gpointer user_data, + GError *error) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + guint recv_timeout = GPOINTER_TO_UINT (user_data); + gsize chunk_len; + + if (error) + { + fpi_ssm_mark_failed (transfer->ssm, error); + return; + } + + chunk_len = (gsize) transfer->actual_length; + if (chunk_len == 0) + { + /* Empty read — re-submit (device can return zero-length packets) */ + fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), + recv_timeout, + fpi_device_get_cancellable (dev), + msg_recv_cb, + user_data); + return; + } + + g_byte_array_append (self->recv_buf, transfer->buffer, (guint) chunk_len); + + /* Check if we have enough data for a complete message. + * A message starts with: [cmd_byte] [size_le16] ... + * total_needed_logical = 3 + message_size (where message_size = payload_len + 1 for checksum) + * + * However, recv_buf contains RAW USB chunks (64 bytes each). + * First chunk: 64 bytes of logical data. + * Each continuation chunk: 1 byte header + 63 bytes of logical data. + * So raw bytes needed = ceil((logical - 64) / 63 + 1) * 64 if logical > 64 + * = 64 if logical <= 64 + */ + if (self->recv_buf->len >= 3) + { + guint16 message_size = GUINT16_FROM_LE (*(guint16 *) &self->recv_buf->data[1]); + gsize total_needed_logical = 3 + (gsize) message_size; + gsize total_needed_raw; + + if (total_needed_logical <= GOODIX53X5_USB_CHUNK_SIZE) + total_needed_raw = GOODIX53X5_USB_CHUNK_SIZE; + else + { + gsize remaining = total_needed_logical - GOODIX53X5_USB_CHUNK_SIZE; + gsize extra_chunks = (remaining + (GOODIX53X5_USB_CHUNK_SIZE - 2)) / (GOODIX53X5_USB_CHUNK_SIZE - 1); + total_needed_raw = (1 + extra_chunks) * GOODIX53X5_USB_CHUNK_SIZE; + } + + if (self->recv_buf->len >= total_needed_raw) + { + /* We have a complete message — advance SSM */ + fpi_ssm_next_state (transfer->ssm); + return; + } + } + + /* Need more data — re-submit the same transfer */ + if (self->recv_buf->len >= GOODIX53X5_MAX_RECV) + { + fpi_ssm_mark_failed (transfer->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: receive buffer overflow")); + return; + } + + fpi_usb_transfer_submit (fpi_usb_transfer_ref (transfer), + recv_timeout, + fpi_device_get_cancellable (dev), + msg_recv_cb, + user_data); +} + +/* + * Post a bulk-IN transfer to receive the next message from the device. + * When the message is complete, the SSM is advanced. + * self->recv_buf is reset before reading. + * + * @timeout: milliseconds to wait for each USB chunk, or 0 for no timeout. + * Use GOODIX53X5_TIMEOUT for normal commands and 0 for states + * that wait for human interaction (e.g. finger placement). + */ +static void +goodix53x5_recv_msg (FpiDeviceGoodix53x5 *self, + guint timeout) +{ + FpiUsbTransfer *transfer; + + g_byte_array_set_size (self->recv_buf, 0); + + transfer = fpi_usb_transfer_new (FP_DEVICE (self)); + transfer->ssm = self->ssm; + fpi_usb_transfer_fill_bulk (transfer, + self->ep_in, + GOODIX53X5_USB_CHUNK_SIZE); + fpi_usb_transfer_submit (transfer, + timeout, + fpi_device_get_cancellable (FP_DEVICE (self)), + msg_recv_cb, + GUINT_TO_POINTER (timeout)); +} + +/* + * Parse the accumulated recv_buf into (category, command, payload). + * Returns TRUE on success; marks the SSM failed on error. + * @out_payload is heap-allocated; caller must g_free(). + */ +static gboolean +goodix53x5_parse_recv (FpiDeviceGoodix53x5 *self, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len) +{ + gboolean ok; + + ok = goodix53x5_parse_message (self->recv_buf->data, + self->recv_buf->len, + out_category, + out_command, + out_payload, + out_payload_len); + if (!ok) + { + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: failed to parse device message")); + } + return ok; +} + +/* + * Parse recv_buf and verify the category/command match expected values. + * On mismatch the SSM is failed. + */ +static gboolean +goodix53x5_check_recv (FpiDeviceGoodix53x5 *self, + guint8 expected_cat, + guint8 expected_cmd, + guint8 **out_payload, + gsize *out_payload_len) +{ + guint8 cat, cmd; + + if (!goodix53x5_parse_recv (self, &cat, &cmd, out_payload, out_payload_len)) + return FALSE; + + if (cat != expected_cat || cmd != expected_cmd) + { + if (out_payload && *out_payload) + { + g_free (*out_payload); + *out_payload = NULL; + } + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: unexpected reply cat=0x%x cmd=0x%x " + "(expected cat=0x%x cmd=0x%x)", + cat, cmd, expected_cat, expected_cmd)); + return FALSE; + } + return TRUE; +} + +/* ============================================================ + * MCU (GTLS) send/recv helpers + * ============================================================ */ + +/* + * Send an MCU message: category=0xD, command=0x1 + * Payload layout: [data_type u32le] [total_len u32le (header+data)] [data] + */ +static void +goodix53x5_send_mcu (FpiDeviceGoodix53x5 *self, + guint32 data_type, + const guint8 *data, + gsize data_len) +{ + guint8 *payload; + gsize payload_len; + guint32 header_and_data_len; + + payload_len = 8 + data_len; + payload = g_malloc (payload_len); + + guint32 type_le = GUINT32_TO_LE (data_type); + memcpy (payload, &type_le, 4); + + header_and_data_len = GUINT32_TO_LE ((guint32) (data_len + 8)); + memcpy (payload + 4, &header_and_data_len, 4); + + if (data_len > 0) + memcpy (payload + 8, data, data_len); + + goodix53x5_send_msg (self, GOODIX53X5_CAT_MCU, GOODIX53X5_CMD_MCU, + payload, payload_len); + g_free (payload); +} + +/* + * Parse an incoming MCU message from recv_buf. + * Returns the inner data (after the 8-byte header) as a newly allocated + * buffer, or NULL on error. Caller must g_free(). + */ +static guint8 * +goodix53x5_parse_mcu_recv (FpiDeviceGoodix53x5 *self, + guint32 expected_type, + gsize *out_data_len) +{ + guint8 *payload = NULL; + gsize payload_len = 0; + guint32 msg_type; + guint32 msg_total_len; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_MCU, + GOODIX53X5_CMD_MCU, + &payload, + &payload_len)) + return NULL; + + if (payload_len < 8) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: MCU reply too short")); + return NULL; + } + + msg_type = GUINT32_FROM_LE (*(guint32 *) payload); + msg_total_len = GUINT32_FROM_LE (*(guint32 *) (payload + 4)); + + if (msg_type != expected_type) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: MCU wrong type 0x%08x " + "(expected 0x%08x)", + msg_type, expected_type)); + return NULL; + } + + if (msg_total_len != (guint32) payload_len) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: MCU length mismatch")); + return NULL; + } + + *out_data_len = payload_len - 8; + guint8 *data = g_malloc (*out_data_len); + memcpy (data, payload + 8, *out_data_len); + g_free (payload); + return data; +} + +/* ============================================================ + * Production read/write helpers + * ============================================================ */ + +/* + * Send a production READ request. + * Payload: [read_type u32le] + */ +static void +goodix53x5_prod_read (FpiDeviceGoodix53x5 *self, + guint32 read_type) +{ + guint8 payload[4]; + guint32 type_le = GUINT32_TO_LE (read_type); + + memcpy (payload, &type_le, 4); + goodix53x5_send_msg (self, GOODIX53X5_CAT_PRODUCTION, + GOODIX53X5_CMD_PROD_READ, + payload, sizeof (payload)); +} + +/* + * Parse a production READ response from recv_buf. + * Returns the data payload on success; caller must g_free(). + */ +static guint8 * +goodix53x5_parse_prod_read_recv (FpiDeviceGoodix53x5 *self, + guint32 expected_type, + gsize *out_len) +{ + guint8 *payload = NULL; + gsize payload_len = 0; + guint32 mcu_status; + guint32 msg_type; + guint32 data_size; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_PRODUCTION, + GOODIX53X5_CMD_PROD_READ, + &payload, + &payload_len)) + return NULL; + + if (payload_len < 9) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: prod-read reply too short")); + return NULL; + } + + mcu_status = payload[0]; + msg_type = GUINT32_FROM_LE (*(guint32 *) (payload + 1)); + data_size = GUINT32_FROM_LE (*(guint32 *) (payload + 5)); + + if (mcu_status != 0) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: prod-read MCU failed (status=%u)", + mcu_status)); + return NULL; + } + + if (msg_type != expected_type) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: prod-read wrong type 0x%08x", + msg_type)); + return NULL; + } + + if (data_size != (guint32) (payload_len - 9)) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: prod-read data size mismatch")); + return NULL; + } + + *out_len = (gsize) data_size; + guint8 *data = g_malloc (*out_len); + memcpy (data, payload + 9, *out_len); + g_free (payload); + return data; +} + +/* + * Send a production WRITE request. + * Payload: [data_type u32le] [data_size u32le] [data] + */ +static void +goodix53x5_prod_write (FpiDeviceGoodix53x5 *self, + guint32 data_type, + const guint8 *data, + gsize data_len) +{ + guint8 *payload; + gsize payload_len = 8 + data_len; + + payload = g_malloc (payload_len); + + guint32 type_le = GUINT32_TO_LE (data_type); + memcpy (payload, &type_le, 4); + + guint32 size_le = GUINT32_TO_LE ((guint32) data_len); + memcpy (payload + 4, &size_le, 4); + + if (data_len > 0) + memcpy (payload + 8, data, data_len); + + goodix53x5_send_msg (self, GOODIX53X5_CAT_PRODUCTION, + GOODIX53X5_CMD_PROD_WRITE, + payload, payload_len); + g_free (payload); +} + +/* + * Parse a production WRITE response — just checks MCU status byte. + */ +static gboolean +goodix53x5_parse_prod_write_recv (FpiDeviceGoodix53x5 *self) +{ + guint8 *payload = NULL; + gsize payload_len = 0; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_PRODUCTION, + GOODIX53X5_CMD_PROD_WRITE, + &payload, + &payload_len)) + return FALSE; + + if (payload_len < 1 || payload[0] != 0) + { + g_free (payload); + fpi_ssm_mark_failed (self->ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: prod-write failed")); + return FALSE; + } + + g_free (payload); + return TRUE; +} + +/* ============================================================ + * Initialization SSM + * ============================================================ */ + +static void +init_ssm_state (FpiSsm *ssm, + FpDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + guint8 img_request[4]; + + switch (fpi_ssm_get_cur_state (ssm)) + { + /* --- ping --- */ + case INIT_PING: + { + guint8 ping_payload[2] = { 0x00, 0x00 }; + goodix53x5_send_msg (self, 0x0, 0x0, ping_payload, 2); + } + break; + + case INIT_PING_RECV: + /* ACK is consumed automatically by goodix53x5_send_msg(); skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- firmware version --- */ + case INIT_FW_VERSION: + { + guint8 fwv_payload[2] = { 0x00, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_FW_VERSION, + fwv_payload, 2); + } + break; + + case INIT_FW_VERSION_RECV: + /* Receive the real firmware version reply (ACK already consumed) */ + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_FW_VERSION_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_FW_VERSION, + &payload, &plen)) + break; + if (plen > 0) + { + guint8 *nul = memchr (payload, 0, plen); + gsize slen = nul ? (gsize) (nul - payload) : plen; + g_autofree gchar *fw_str = g_strndup ((gchar *) payload, slen); + fp_dbg ("goodix53x5: firmware version: %s", fw_str); + } + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- soft reset --- */ + case INIT_RESET: + { + /* reset_type=0: msg = 0b001 | (20 << 8) = 0x1401 */ + guint16 rst_le = GUINT16_TO_LE (0x1401); + goodix53x5_send_msg (self, GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_RESET, + (guint8 *) &rst_le, 2); + } + break; + + case INIT_RESET_RECV: + /* ACK is consumed automatically by goodix53x5_send_msg(); skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- read chip ID --- */ + case INIT_READ_CHIP_ID: + { + guint8 rd_payload[5]; + rd_payload[0] = 0x00; + guint16 addr_le = GUINT16_TO_LE (0x0000); + guint16 size_le = GUINT16_TO_LE (0x0004); + memcpy (&rd_payload[1], &addr_le, 2); + memcpy (&rd_payload[3], &size_le, 2); + goodix53x5_send_msg (self, GOODIX53X5_CAT_READ_REG, + GOODIX53X5_CMD_READ_REG, + rd_payload, 5); + } + break; + + case INIT_READ_CHIP_ID_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_READ_CHIP_ID_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_READ_REG, + GOODIX53X5_CMD_READ_REG, + &payload, &plen)) + break; + if (plen >= 4) + { + guint32 chip_id = GUINT32_FROM_LE (*(guint32 *) payload); + fp_dbg ("goodix53x5: chip ID: 0x%08x", chip_id); + } + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- read OTP --- */ + case INIT_READ_OTP: + { + guint8 otp_payload[2] = { 0x00, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_READ_OTP, + otp_payload, 2); + } + break; + + case INIT_READ_OTP_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_READ_OTP_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_READ_OTP, + &payload, &plen)) + break; + if (!goodix53x5_parse_otp (payload, plen, &self->calib)) + { + g_free (payload); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: OTP parse failed")); + break; + } + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- read PSK hash to verify --- */ + case INIT_READ_PSK_HASH: + goodix53x5_prod_read (self, GOODIX53X5_PROD_PSK_HASH); + break; + + case INIT_READ_PSK_HASH_RECV: + /* Async receive — SSM advances automatically when data arrives */ + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_READ_PSK_HASH_PARSE: + { + guint8 *hash = NULL; + gsize hash_len = 0; + static const guint8 expected_hash[32] = GOODIX53X5_PSK_HASH; + + hash = goodix53x5_parse_prod_read_recv (self, GOODIX53X5_PROD_PSK_HASH, &hash_len); + if (!hash) + break; /* SSM already failed */ + + if (hash_len != 32 || memcmp (hash, expected_hash, 32) != 0) + { + fp_info ("goodix53x5: PSK hash mismatch, will write white-box PSK"); + self->need_psk_write = TRUE; + } + else + { + fp_dbg ("goodix53x5: PSK hash OK"); + self->need_psk_write = FALSE; + } + g_free (hash); + fpi_ssm_next_state (ssm); + } + break; + + /* --- write PSK white-box (only if needed) --- */ + case INIT_WRITE_PSK: + if (self->need_psk_write) + { + static const guint8 psk_wb[GOODIX53X5_PSK_WHITE_BOX_LEN] = GOODIX53X5_PSK_WHITE_BOX; + goodix53x5_prod_write (self, GOODIX53X5_PROD_PSK_WHITE_BOX, psk_wb, + GOODIX53X5_PSK_WHITE_BOX_LEN); + } + else + { + fpi_ssm_next_state (ssm); + } + break; + + case INIT_WRITE_PSK_RECV: + if (self->need_psk_write) + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + else + fpi_ssm_next_state (ssm); + break; + + case INIT_WRITE_PSK_PARSE: + if (self->need_psk_write) + { + if (!goodix53x5_parse_prod_write_recv (self)) + break; + } + fpi_ssm_next_state (ssm); + break; + + /* --- GTLS client hello --- */ + case INIT_GTLS_CLIENT_HELLO: + { + /* Generate 32 random bytes for client_random */ + for (guint i = 0; i < GOODIX53X5_RANDOM_LEN; i++) + self->gtls.client_random[i] = (guint8) g_random_int (); + + memset (self->gtls.psk, 0, GOODIX53X5_PSK_LEN); /* all-zero PSK */ + + goodix53x5_send_mcu (self, GOODIX53X5_MCU_CLIENT_HELLO, + self->gtls.client_random, + GOODIX53X5_RANDOM_LEN); + } + break; + + case INIT_GTLS_CLIENT_HELLO_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- GTLS server identity --- */ + case INIT_GTLS_SERVER_IDENTITY_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_GTLS_SERVER_IDENTITY_PARSE: + { + guint8 *data = NULL; + gsize data_len = 0; + guint8 computed_identity[GOODIX53X5_IDENTITY_LEN]; + + data = goodix53x5_parse_mcu_recv (self, GOODIX53X5_MCU_SERVER_IDENTITY, &data_len); + if (!data) + break; + + if (data_len < GOODIX53X5_RANDOM_LEN + GOODIX53X5_IDENTITY_LEN) + { + g_free (data); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: server identity too short")); + break; + } + + memcpy (self->gtls.server_random, data, GOODIX53X5_RANDOM_LEN); + + /* Derive session key */ + goodix53x5_gtls_derive_session_key (self->gtls.psk, + self->gtls.client_random, + self->gtls.server_random, + &self->gtls); + + /* Compute client identity = HMAC(hmac_key, client_random || server_random) */ + goodix53x5_gtls_compute_identity (self->gtls.hmac_key, + self->gtls.client_random, + self->gtls.server_random, + computed_identity); + + /* Verify server identity matches */ + const guint8 *server_identity = data + GOODIX53X5_RANDOM_LEN; + if (memcmp (server_identity, computed_identity, GOODIX53X5_IDENTITY_LEN) != 0) + { + g_free (data); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: GTLS identity mismatch " + "(wrong PSK?)")); + break; + } + + g_free (data); + fpi_ssm_next_state (ssm); + } + break; + + /* --- GTLS client identity --- */ + case INIT_GTLS_CLIENT_IDENTITY: + { + /* Send identity + 4 padding bytes (0xEE) */ + guint8 identity_payload[GOODIX53X5_IDENTITY_LEN + 4]; + guint8 identity[GOODIX53X5_IDENTITY_LEN]; + + goodix53x5_gtls_compute_identity (self->gtls.hmac_key, + self->gtls.client_random, + self->gtls.server_random, + identity); + + memcpy (identity_payload, identity, GOODIX53X5_IDENTITY_LEN); + memset (identity_payload + GOODIX53X5_IDENTITY_LEN, 0xEE, 4); + + goodix53x5_send_mcu (self, GOODIX53X5_MCU_CLIENT_IDENTITY, + identity_payload, + sizeof (identity_payload)); + } + break; + + case INIT_GTLS_CLIENT_IDENTITY_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- GTLS server done --- */ + case INIT_GTLS_SERVER_DONE_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_GTLS_SERVER_DONE_PARSE: + { + guint8 *data = NULL; + gsize data_len = 0; + guint32 result; + + data = goodix53x5_parse_mcu_recv (self, GOODIX53X5_MCU_SERVER_DONE, &data_len); + if (!data) + break; + + if (data_len < 4) + { + g_free (data); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: server-done too short")); + break; + } + + result = GUINT32_FROM_LE (*(guint32 *) data); + g_free (data); + + if (result != 0) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: GTLS handshake failed " + "(result=%u)", result)); + break; + } + + self->gtls.connected = TRUE; + fp_dbg ("goodix53x5: GTLS handshake successful"); + fpi_ssm_next_state (ssm); + } + break; + + /* --- upload config --- */ + case INIT_UPLOAD_CONFIG: + { + guint8 config[256]; + goodix53x5_build_config (&self->calib, config); + goodix53x5_send_msg (self, GOODIX53X5_CAT_CONFIG, + GOODIX53X5_CMD_CONFIG_UPLOAD, + config, 256); + } + break; + + case INIT_UPLOAD_CONFIG_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_UPLOAD_CONFIG_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_CONFIG, + GOODIX53X5_CMD_CONFIG_UPLOAD, + &payload, &plen)) + break; + if (plen < 1) + { + g_free (payload); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: config reply too short")); + break; + } + /* Result is returned as a little-endian integer of whatever length the device sends */ + guint32 result = 0; + for (gsize ri = 0; ri < MIN (plen, (gsize) 4); ri++) + result |= ((guint32) payload[ri]) << (ri * 8); + g_free (payload); + if (result != 1) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: config upload failed (result=%u)", + result)); + break; + } + fp_dbg ("goodix53x5: config uploaded"); + fpi_ssm_next_state (ssm); + } + break; + + /* --- calibration: FDT manual TX on --- */ + case INIT_CALIB_FDT_DOWN_TX_ON: + { + /* execute_fdt_operation(MANUAL, [0x0D, 0x01, fdt_base_manual]) */ + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_MANUAL_TX_ON; /* opcode */ + fdt_payload[1] = 0x01; /* always 1 */ + memcpy (&fdt_payload[2], self->calib.fdt_base_manual, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case INIT_CALIB_FDT_DOWN_TX_ON_RECV: + /* Async receive; SSM advances when data arrives */ + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_CALIB_FDT_DOWN_TX_ON_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + &payload, &plen)) + break; + if (plen >= 4 + GOODIX53X5_FDT_BASE_LEN) + { + /* payload: [irq_status u16] [touch_flag u16] [fdt_data 24 bytes] */ + const guint8 *fdt_data = payload + 4; + /* Store as fdt_base_down, fdt_base_up, fdt_base_manual */ + goodix53x5_generate_fdt_base (fdt_data, self->calib.fdt_base_down); + memcpy (self->calib.fdt_base_up, self->calib.fdt_base_down, GOODIX53X5_FDT_BASE_LEN); + memcpy (self->calib.fdt_base_manual, self->calib.fdt_base_down, GOODIX53X5_FDT_BASE_LEN); + } + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- calibration: get image TX on --- */ + case INIT_CALIB_IMG_TX_ON: + { + /* get_image(tx=True, hv=True, dac=dac_l) */ + img_request[0] = GOODIX53X5_IMG_TX_ENABLE; + img_request[1] = GOODIX53X5_IMG_HV_VALUE; + guint16 dac_le = GUINT16_TO_LE (self->calib.dac_l); + memcpy (&img_request[2], &dac_le, 2); + goodix53x5_send_msg (self, GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + img_request, 4); + } + break; + + case INIT_CALIB_IMG_TX_ON_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_CALIB_IMG_TX_ON_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + &payload, &plen)) + break; + /* Decrypt to advance hmac_server_counter; discard the image itself */ + guint8 *raw = NULL; + gsize raw_len = 0; + if (!goodix53x5_gtls_decrypt_image (&self->gtls, payload, plen, &raw, &raw_len)) + fp_warn ("goodix53x5: calibration image TX-on HMAC failed (non-fatal)"); + g_free (raw); + g_free (payload); + fp_dbg ("goodix53x5: calibration image TX-on received"); + fpi_ssm_next_state (ssm); + } + break; + + /* --- calibration: FDT manual TX off --- */ + case INIT_CALIB_FDT_DOWN_TX_OFF: + { + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_MANUAL_TX_OFF; + fdt_payload[1] = 0x01; + memcpy (&fdt_payload[2], self->calib.fdt_base_manual, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case INIT_CALIB_FDT_DOWN_TX_OFF_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_CALIB_FDT_DOWN_TX_OFF_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + &payload, &plen)) + break; + + if (plen >= 4 + GOODIX53X5_FDT_BASE_LEN) + { + const guint8 *fdt_tx_off = payload + 4; + /* Validate FDT bases are consistent */ + if (!goodix53x5_is_fdt_base_valid (self->calib.fdt_base_down, + fdt_tx_off, + self->calib.delta_fdt)) + { + fp_warn ("goodix53x5: FDT base validation failed during calibration " + "(delta_fdt=%u)", self->calib.delta_fdt); + /* Non-fatal: continue anyway */ + } + } + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- calibration: image TX off --- */ + case INIT_CALIB_IMG_TX_OFF: + { + img_request[0] = GOODIX53X5_IMG_TX_DISABLE; + img_request[1] = GOODIX53X5_IMG_HV_VALUE; + guint16 dac_le = GUINT16_TO_LE (self->calib.dac_l); + memcpy (&img_request[2], &dac_le, 2); + goodix53x5_send_msg (self, GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + img_request, 4); + } + break; + + case INIT_CALIB_IMG_TX_OFF_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_CALIB_IMG_TX_OFF_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + &payload, &plen)) + break; + /* Decrypt to advance hmac_server_counter; discard the image itself */ + guint8 *raw = NULL; + gsize raw_len = 0; + if (!goodix53x5_gtls_decrypt_image (&self->gtls, payload, plen, &raw, &raw_len)) + fp_warn ("goodix53x5: calibration image TX-off HMAC failed (non-fatal)"); + g_free (raw); + g_free (payload); + fp_dbg ("goodix53x5: calibration image TX-off received"); + fpi_ssm_next_state (ssm); + } + break; + + /* --- calibration: second FDT TX on (cross-check) --- */ + case INIT_CALIB_FDT_DOWN_TX_ON_2: + { + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_MANUAL_TX_ON; + fdt_payload[1] = 0x01; + memcpy (&fdt_payload[2], self->calib.fdt_base_manual, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case INIT_CALIB_FDT_DOWN_TX_ON_2_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case INIT_CALIB_FDT_DOWN_TX_ON_2_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + &payload, &plen)) + break; + g_free (payload); + fp_dbg ("goodix53x5: calibration FDT 2nd TX-on done"); + fpi_ssm_next_state (ssm); + } + break; + + /* --- sleep --- */ + case INIT_SLEEP: + { + guint8 sleep_payload[2] = { 0x01, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_SLEEP, + GOODIX53X5_CMD_SLEEP, + sleep_payload, 2); + } + break; + + case INIT_SLEEP_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + default: + g_assert_not_reached (); + } +} + +static void +init_ssm_done (FpiSsm *ssm, + FpDevice *dev, + GError *error) +{ + FpImageDevice *idev = FP_IMAGE_DEVICE (dev); + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + + self->ssm = NULL; + + if (error) + { + fp_err ("goodix53x5: initialization failed: %s", error->message); + fpi_image_device_open_complete (idev, error); + return; + } + + fp_dbg ("goodix53x5: initialization complete"); + fpi_image_device_open_complete (idev, NULL); +} + +/* ============================================================ + * Capture SSM + * ============================================================ */ + +static void +capture_ssm_done (FpiSsm *ssm, + FpDevice *dev, + GError *error) +{ + FpImageDevice *idev = FP_IMAGE_DEVICE (dev); + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + + self->ssm = NULL; + + if (self->deactivating) + { + fpi_image_device_deactivate_complete (idev, NULL); + return; + } + + if (error) + { + fpi_image_device_session_error (idev, error); + return; + } + + /* Start another capture cycle */ + goodix53x5_start_capture_ssm (idev); +} + +static void +capture_ssm_state (FpiSsm *ssm, + FpDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + FpImageDevice *idev = FP_IMAGE_DEVICE (dev); + guint8 img_request[4]; + + if (self->deactivating) + { + fpi_ssm_mark_completed (ssm); + return; + } + + switch (fpi_ssm_get_cur_state (ssm)) + { + /* --- power on EC --- */ + case CAPTURE_EC_ON: + { + /* ec_control("on"): payload = [0x01, 0x01, 0x00] */ + guint8 ec_payload[3] = { 0x01, 0x01, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_EC_CONTROL, + ec_payload, 3); + } + break; + + case CAPTURE_EC_ON_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case CAPTURE_EC_ON_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_EC_CONTROL, + &payload, &plen)) + break; + if (plen < 1) + { + g_free (payload); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: EC on failed (reply too short)")); + break; + } + { + guint32 result = 0; + for (gsize ri = 0; ri < MIN (plen, (gsize) 4); ri++) + result |= ((guint32) payload[ri]) << (ri * 8); + g_free (payload); + if (result != 1) + { + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: EC on failed (result=%u)", + result)); + break; + } + } + fpi_ssm_next_state (ssm); + } + break; + + /* --- setup FDT down --- */ + case CAPTURE_FDT_DOWN: + { + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_DOWN_OPCODE; + fdt_payload[1] = 0x01; + memcpy (&fdt_payload[2], self->calib.fdt_base_down, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_DOWN, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case CAPTURE_FDT_DOWN_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- wait for finger down event --- */ + case CAPTURE_WAIT_FINGER_DOWN: + goodix53x5_recv_msg (self, 0); /* no timeout: waiting for human finger */ + break; + + case CAPTURE_WAIT_FINGER_DOWN_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_DOWN, + &payload, &plen)) + break; + + if (plen >= 4 + GOODIX53X5_FDT_BASE_LEN) + { + guint16 touch_flag = GUINT16_FROM_LE (*(guint16 *) (payload + 2)); + const guint8 *fdt_data = payload + 4; + + /* Build fdt_base_up from finger-down event */ + goodix53x5_generate_fdt_up_base (fdt_data, touch_flag, + &self->calib, + self->calib.fdt_base_up); + } + g_free (payload); + + fpi_image_device_report_finger_status (idev, TRUE); + fpi_ssm_next_state (ssm); + } + break; + + /* --- manual FDT to verify real finger --- */ + case CAPTURE_MANUAL_FDT: + { + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_MANUAL_TX_OFF; + fdt_payload[1] = 0x01; + memcpy (&fdt_payload[2], self->calib.fdt_base_manual, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case CAPTURE_MANUAL_FDT_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case CAPTURE_MANUAL_FDT_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_MANUAL, + &payload, &plen)) + break; + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + /* --- capture image --- */ + case CAPTURE_GET_IMAGE: + { + /* get_image(tx=True, hv=True, is_finger=True, dac=dac_h) */ + img_request[0] = GOODIX53X5_IMG_TX_ENABLE | GOODIX53X5_IMG_FINGER_FLAG; + img_request[1] = GOODIX53X5_IMG_HV_VALUE; + guint16 dac_le = GUINT16_TO_LE (self->calib.dac_h); + memcpy (&img_request[2], &dac_le, 2); + goodix53x5_send_msg (self, GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + img_request, 4); + } + break; + + case CAPTURE_GET_IMAGE_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case CAPTURE_GET_IMAGE_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + guint8 *raw = NULL; + gsize raw_len = 0; + guint16 *pixels = NULL; + + fp_dbg ("goodix53x5: image recv_buf len=%u, first bytes: %02x %02x %02x %02x", + self->recv_buf->len, + self->recv_buf->len > 0 ? self->recv_buf->data[0] : 0, + self->recv_buf->len > 1 ? self->recv_buf->data[1] : 0, + self->recv_buf->len > 2 ? self->recv_buf->data[2] : 0, + self->recv_buf->len > 3 ? self->recv_buf->data[3] : 0); + + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_IMAGE, + GOODIX53X5_CMD_IMAGE_GET, + &payload, &plen)) + break; + + /* Decrypt the image using GTLS context */ + if (!goodix53x5_gtls_decrypt_image (&self->gtls, payload, plen, &raw, &raw_len)) + { + g_free (payload); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: image decrypt failed")); + break; + } + g_free (payload); + + /* Decode 12-bit packed pixels */ + if (!goodix53x5_decode_image (raw, raw_len, &pixels)) + { + g_free (raw); + fpi_ssm_mark_failed (ssm, + fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO, + "goodix53x5: image decode failed")); + break; + } + g_free (raw); + + /* Convert 12-bit pixel data to 8-bit for libfprint */ + FpImage *img = fp_image_new (GOODIX53X5_SENSOR_WIDTH, GOODIX53X5_SENSOR_HEIGHT); + for (guint i = 0; i < GOODIX53X5_SENSOR_PIXELS; i++) + img->data[i] = (guint8) (pixels[i] >> 4); /* 12→8 bit */ + g_free (pixels); + + fpi_image_device_image_captured (idev, img); + fpi_ssm_next_state (ssm); + } + break; + + /* --- setup FDT up --- */ + case CAPTURE_FDT_UP: + { + guint8 fdt_payload[2 + GOODIX53X5_FDT_BASE_LEN]; + fdt_payload[0] = GOODIX53X5_FDT_UP_OPCODE; + fdt_payload[1] = 0x01; + memcpy (&fdt_payload[2], self->calib.fdt_base_up, GOODIX53X5_FDT_BASE_LEN); + goodix53x5_send_msg (self, GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_UP, + fdt_payload, sizeof (fdt_payload)); + } + break; + + case CAPTURE_FDT_UP_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- wait for finger up event --- */ + case CAPTURE_WAIT_FINGER_UP: + goodix53x5_recv_msg (self, 0); /* no timeout: waiting for human finger removal */ + break; + + case CAPTURE_WAIT_FINGER_UP_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_FDT, + GOODIX53X5_CMD_FDT_UP, + &payload, &plen)) + break; + + if (plen >= 4 + GOODIX53X5_FDT_BASE_LEN) + { + const guint8 *fdt_data = payload + 4; + /* Update fdt_base_down from finger-up event */ + goodix53x5_generate_fdt_base (fdt_data, self->calib.fdt_base_down); + } + g_free (payload); + + fpi_image_device_report_finger_status (idev, FALSE); + fpi_ssm_next_state (ssm); + } + break; + + /* --- sleep --- */ + case CAPTURE_SLEEP: + { + guint8 sleep_payload[2] = { 0x01, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_SLEEP, + GOODIX53X5_CMD_SLEEP, + sleep_payload, 2); + } + break; + + case CAPTURE_SLEEP_RECV: + /* ACK consumed automatically by send_cb; skip through */ + fpi_ssm_next_state (ssm); + break; + + /* --- power off EC --- */ + case CAPTURE_EC_OFF: + { + guint8 ec_payload[3] = { 0x00, 0x00, 0x00 }; + goodix53x5_send_msg (self, GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_EC_CONTROL, + ec_payload, 3); + } + break; + + case CAPTURE_EC_OFF_RECV: + goodix53x5_recv_msg (self, GOODIX53X5_TIMEOUT); + break; + + case CAPTURE_EC_OFF_PARSE: + { + guint8 *payload = NULL; + gsize plen = 0; + if (!goodix53x5_check_recv (self, + GOODIX53X5_CAT_RESET, + GOODIX53X5_CMD_EC_CONTROL, + &payload, &plen)) + break; + g_free (payload); + fpi_ssm_next_state (ssm); + } + break; + + default: + g_assert_not_reached (); + } +} + +static void +goodix53x5_start_capture_ssm (FpImageDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + FpiSsm *ssm; + + ssm = fpi_ssm_new (FP_DEVICE (dev), capture_ssm_state, CAPTURE_NUM_STATES); + self->ssm = ssm; + fpi_ssm_start (ssm, capture_ssm_done); +} + +/* ============================================================ + * FpImageDevice vfuncs + * ============================================================ */ + +static void +goodix53x5_img_open (FpImageDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + GUsbDevice *usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + GError *error = NULL; + + if (NSS_NoDB_Init (".") != SECSuccess) + { + fpi_image_device_open_complete (dev, + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Failed to initialise NSS")); + return; + } + + /* Reset the device to clear any stale state from a previous session. + * The device may be unresponsive without this. */ + if (!g_usb_device_reset (usb_dev, &error)) + { + /* Non-fatal: log and continue — the device may still work */ + fp_warn ("goodix53x5: USB reset failed: %s", error->message); + g_clear_error (&error); + } + + /* The device presents as a CDC device with two interfaces: + * Interface 0: CDC Communications (interrupt EP 0x82) + * Interface 1: CDC Data (bulk OUT 0x03, bulk IN 0x81) + * The kernel cdc_acm driver claims both. We must claim both to detach it. + */ + if (!g_usb_device_claim_interface (usb_dev, 0, + G_USB_DEVICE_CLAIM_INTERFACE_NONE, + &error)) + { + fpi_image_device_open_complete (dev, error); + return; + } + + if (!g_usb_device_claim_interface (usb_dev, 1, + G_USB_DEVICE_CLAIM_INTERFACE_NONE, + &error)) + { + g_usb_device_release_interface (usb_dev, 0, 0, NULL); + fpi_image_device_open_complete (dev, error); + return; + } + + self->ep_in = GOODIX53X5_EP_IN; + self->ep_out = GOODIX53X5_EP_OUT; + self->recv_buf = g_byte_array_new (); + self->deactivating = FALSE; + self->need_psk_write = FALSE; + memset (&self->gtls, 0, sizeof (self->gtls)); + memset (&self->calib, 0, sizeof (self->calib)); + + FpiSsm *ssm = fpi_ssm_new (FP_DEVICE (dev), init_ssm_state, INIT_NUM_STATES); + self->ssm = ssm; + fpi_ssm_start (ssm, init_ssm_done); +} + +static void +goodix53x5_img_close (FpImageDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + GUsbDevice *usb_dev = fpi_device_get_usb_device (FP_DEVICE (dev)); + GError *error = NULL; + + g_clear_pointer (&self->recv_buf, g_byte_array_unref); + + g_usb_device_release_interface (usb_dev, 1, 0, &error); + if (error) + { + g_usb_device_release_interface (usb_dev, 0, 0, NULL); + fpi_image_device_close_complete (dev, error); + return; + } + g_usb_device_release_interface (usb_dev, 0, 0, &error); + fpi_image_device_close_complete (dev, error); + NSS_Shutdown (); +} + +static void +goodix53x5_activate (FpImageDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + + self->deactivating = FALSE; + fpi_image_device_activate_complete (dev, NULL); +} + +static void +goodix53x5_deactivate (FpImageDevice *dev) +{ + FpiDeviceGoodix53x5 *self = FPI_DEVICE_GOODIX53X5 (dev); + + self->deactivating = TRUE; + + if (self->ssm == NULL) + { + /* No SSM running — complete immediately */ + fpi_image_device_deactivate_complete (dev, NULL); + } + /* Otherwise the running SSM will see deactivating==TRUE and finish up */ +} + +static void +goodix53x5_change_state (FpImageDevice *dev, + FpiImageDeviceState state) +{ + if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) + goodix53x5_start_capture_ssm (dev); +} + +/* ============================================================ + * Device ID table and class initialisation + * ============================================================ */ + +static const FpIdEntry id_table[] = { + { .vid = 0x27c6, .pid = 0x5395 }, + { .vid = 0x27c6, .pid = 0x5335 }, + { .vid = 0x27c6, .pid = 0x5385 }, + { .vid = 0, .pid = 0, .driver_data = 0 }, +}; + +static void +fpi_device_goodix53x5_init (FpiDeviceGoodix53x5 *self) +{ + (void) self; +} + +static void +fpi_device_goodix53x5_class_init (FpiDeviceGoodix53x5Class *klass) +{ + FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass); + FpImageDeviceClass *img_class = FP_IMAGE_DEVICE_CLASS (klass); + + dev_class->id = FP_COMPONENT; + dev_class->full_name = "Goodix 53x5 series"; + dev_class->type = FP_DEVICE_TYPE_USB; + dev_class->id_table = id_table; + dev_class->scan_type = FP_SCAN_TYPE_PRESS; + + img_class->img_width = GOODIX53X5_SENSOR_WIDTH; + img_class->img_height = GOODIX53X5_SENSOR_HEIGHT; + + img_class->bz3_threshold = 24; + + img_class->img_open = goodix53x5_img_open; + img_class->img_close = goodix53x5_img_close; + img_class->activate = goodix53x5_activate; + img_class->deactivate = goodix53x5_deactivate; + img_class->change_state = goodix53x5_change_state; +} diff --git a/libfprint/drivers/goodix53x5/goodix53x5.h b/libfprint/drivers/goodix53x5/goodix53x5.h new file mode 100644 index 00000000..3897a299 --- /dev/null +++ b/libfprint/drivers/goodix53x5/goodix53x5.h @@ -0,0 +1,168 @@ +/* + * Goodix 53x5 series driver for libfprint + * Copyright (C) 2024 libfprint contributors + * + * Based on the reference Python implementation by goodix-fp-dump contributors + * https://github.com/nickel-org/goodix-fp-dump + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "drivers_api.h" +#include "goodix53x5_proto.h" + +G_DECLARE_FINAL_TYPE (FpiDeviceGoodix53x5, fpi_device_goodix53x5, + FPI, DEVICE_GOODIX53X5, FpImageDevice) + +/* + * PSK white-box blob (96 bytes). + * This encodes the all-zero 32-byte PSK in the device's white-box format. + */ +#define GOODIX53X5_PSK_WHITE_BOX \ + "\xec\x35\xae\x3a\xbb\x45\xed\x3f\x12\xc4\x75\x1f\x1e\x5c\x2c\xc0" \ + "\x5b\x3c\x54\x52\xe9\x10\x4d\x9f\x2a\x31\x18\x64\x4f\x37\xa0\x4b" \ + "\x6f\xd6\x6b\x1d\x97\xcf\x80\xf1\x34\x5f\x76\xc8\x4f\x03\xff\x30" \ + "\xbb\x51\xbf\x30\x8f\x2a\x98\x75\xc4\x1e\x65\x92\xcd\x2a\x2f\x9e" \ + "\x60\x80\x9b\x17\xb5\x31\x60\x37\xb6\x9b\xb2\xfa\x5d\x4c\x8a\xc3" \ + "\x1e\xdb\x33\x94\x04\x6e\xc0\x6b\xbd\xac\xc5\x7d\xa6\xa7\x56\xc5" + +/* SHA-256 of the all-zero 32-byte PSK */ +#define GOODIX53X5_PSK_HASH \ + "\x66\x68\x7a\xad\xf8\x62\xbd\x77\x6c\x8f\xc1\x8b\x8e\x9f\x8e\x20" \ + "\x08\x97\x14\x85\x6e\xe2\x33\xb3\x90\x2a\x59\x1d\x0d\x5f\x29\x25" + +/* + * Init SSM states — run once on img_open: + * ping → read firmware version → soft reset → read chip ID → + * read OTP → verify/write PSK → GTLS handshake → upload config → + * calibration (FDT+images) → sleep → done + */ +typedef enum { + INIT_PING = 0, + INIT_PING_RECV, /* dead — ACK consumed by send_cb */ + INIT_FW_VERSION, + INIT_FW_VERSION_RECV, /* posts recv for real reply */ + INIT_FW_VERSION_PARSE, /* parses firmware version string */ + INIT_RESET, + INIT_RESET_RECV, /* dead — ACK consumed by send_cb */ + INIT_READ_CHIP_ID, + INIT_READ_CHIP_ID_RECV, /* posts recv for real reply */ + INIT_READ_CHIP_ID_PARSE, /* logs chip ID */ + INIT_READ_OTP, + INIT_READ_OTP_RECV, /* posts recv for real reply */ + INIT_READ_OTP_PARSE, /* parses OTP calibration data */ + INIT_READ_PSK_HASH, + INIT_READ_PSK_HASH_RECV, /* posts recv for prod-read reply */ + INIT_READ_PSK_HASH_PARSE, /* validates hash, sets need_psk_write */ + INIT_WRITE_PSK, /* only entered if PSK hash mismatch */ + INIT_WRITE_PSK_RECV, /* posts recv for prod-write reply */ + INIT_WRITE_PSK_PARSE, /* validates write result */ + INIT_GTLS_CLIENT_HELLO, + INIT_GTLS_CLIENT_HELLO_RECV, /* dead — ACK consumed by send_cb */ + INIT_GTLS_SERVER_IDENTITY_RECV, /* posts recv for server identity push */ + INIT_GTLS_SERVER_IDENTITY_PARSE, /* derives session key + verifies identity */ + INIT_GTLS_CLIENT_IDENTITY, + INIT_GTLS_CLIENT_IDENTITY_RECV, /* dead — ACK consumed by send_cb */ + INIT_GTLS_SERVER_DONE_RECV, /* posts recv for server-done push */ + INIT_GTLS_SERVER_DONE_PARSE, /* verifies handshake result */ + INIT_UPLOAD_CONFIG, + INIT_UPLOAD_CONFIG_RECV, /* posts recv for config reply */ + INIT_UPLOAD_CONFIG_PARSE, /* validates config result */ + INIT_CALIB_FDT_DOWN_TX_ON, + INIT_CALIB_FDT_DOWN_TX_ON_RECV, /* posts recv */ + INIT_CALIB_FDT_DOWN_TX_ON_PARSE, /* extracts FDT base data */ + INIT_CALIB_IMG_TX_ON, + INIT_CALIB_IMG_TX_ON_RECV, /* posts recv */ + INIT_CALIB_IMG_TX_ON_PARSE, /* discards calibration image */ + INIT_CALIB_FDT_DOWN_TX_OFF, + INIT_CALIB_FDT_DOWN_TX_OFF_RECV, /* posts recv */ + INIT_CALIB_FDT_DOWN_TX_OFF_PARSE,/* validates FDT bases */ + INIT_CALIB_IMG_TX_OFF, + INIT_CALIB_IMG_TX_OFF_RECV, /* posts recv */ + INIT_CALIB_IMG_TX_OFF_PARSE, /* discards calibration image */ + INIT_CALIB_FDT_DOWN_TX_ON_2, + INIT_CALIB_FDT_DOWN_TX_ON_2_RECV, /* posts recv */ + INIT_CALIB_FDT_DOWN_TX_ON_2_PARSE, /* discards second FDT event */ + INIT_SLEEP, + INIT_SLEEP_RECV, /* dead — ACK consumed by send_cb */ + INIT_NUM_STATES, +} FpiDeviceGoodix53x5InitState; + +/* + * Capture SSM states — run for each finger-down / finger-up cycle: + * EC on → FDT down → wait FDT event → read image → FDT up → + * wait FDT up event → sleep → EC off → report + */ +typedef enum { + CAPTURE_EC_ON = 0, + CAPTURE_EC_ON_RECV, /* posts recv for real EC-on reply */ + CAPTURE_EC_ON_PARSE, /* verifies EC-on reply */ + CAPTURE_FDT_DOWN, + CAPTURE_FDT_DOWN_RECV, /* dead — ACK consumed by send_cb */ + CAPTURE_WAIT_FINGER_DOWN, /* posts recv for FDT down event */ + CAPTURE_WAIT_FINGER_DOWN_PARSE,/* processes FDT event, reports finger on */ + CAPTURE_MANUAL_FDT, + CAPTURE_MANUAL_FDT_RECV, /* posts recv for manual FDT reply */ + CAPTURE_MANUAL_FDT_PARSE, /* discards manual FDT reply */ + CAPTURE_GET_IMAGE, + CAPTURE_GET_IMAGE_RECV, /* posts recv for image reply */ + CAPTURE_GET_IMAGE_PARSE, /* decrypts + decodes + reports image */ + CAPTURE_FDT_UP, + CAPTURE_FDT_UP_RECV, /* dead — ACK consumed by send_cb */ + CAPTURE_WAIT_FINGER_UP, /* posts recv for FDT up event */ + CAPTURE_WAIT_FINGER_UP_PARSE, /* processes FDT up event, reports finger off */ + CAPTURE_SLEEP, + CAPTURE_SLEEP_RECV, /* dead — ACK consumed by send_cb */ + CAPTURE_EC_OFF, + CAPTURE_EC_OFF_RECV, /* posts recv for real EC-off reply */ + CAPTURE_EC_OFF_PARSE, /* verifies EC-off reply */ + CAPTURE_NUM_STATES, +} FpiDeviceGoodix53x5CaptureState; + +/* + * Per-device instance data + */ +struct _FpiDeviceGoodix53x5 +{ + FpImageDevice parent; + + /* USB endpoints (discovered during open) */ + guint8 ep_in; + guint8 ep_out; + + /* Current running SSM */ + FpiSsm *ssm; + + /* Receive buffer — large enough for the biggest message we expect + * (encrypted image: ~0x5000+ bytes in many chunks) */ + GByteArray *recv_buf; + + /* Calibration data derived from OTP */ + Goodix53x5CalibParams calib; + + /* GTLS context — valid after handshake */ + Goodix53x5GTLSCtx gtls; + + /* Deactivation requested flag */ + gboolean deactivating; + + /* PSK hash mismatch: need to write white-box PSK */ + gboolean need_psk_write; + + /* First FDT event data from finger-down (used to build fdt_base_up) */ + guint8 fdt_event_data[GOODIX53X5_FDT_BASE_LEN + 4]; /* 4 header bytes */ +}; diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.c b/libfprint/drivers/goodix53x5/goodix53x5_proto.c new file mode 100644 index 00000000..48644efe --- /dev/null +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.c @@ -0,0 +1,1084 @@ +/* + * Goodix 53x5 series driver for libfprint + * Copyright (C) 2024 libfprint contributors + * + * Based on the reference Python implementation by goodix-fp-dump contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define FP_COMPONENT "goodix53x5" +#include "fpi-log.h" + +#include "goodix53x5_proto.h" + +#include +#include +#include +#include + +/* ---------- Default sensor config (256 bytes from reference impl) ---------- */ +const guint8 goodix53x5_default_config[256] = { + 0x40, 0x11, 0x6c, 0x7d, 0x28, 0xa5, 0x28, 0xcd, 0x1c, 0xe9, 0x10, 0xf9, 0x00, 0xf9, 0x00, 0xf9, + 0x00, 0x04, 0x02, 0x00, 0x00, 0x08, 0x00, 0x11, 0x11, 0xba, 0x00, 0x01, 0x80, 0xca, 0x00, 0x07, + 0x00, 0x84, 0x00, 0xbe, 0xb2, 0x86, 0x00, 0xc5, 0xb9, 0x88, 0x00, 0xb5, 0xad, 0x8a, 0x00, 0x9d, + 0x95, 0x8c, 0x00, 0x00, 0xbe, 0x8e, 0x00, 0x00, 0xc5, 0x90, 0x00, 0x00, 0xb5, 0x92, 0x00, 0x00, + 0x9d, 0x94, 0x00, 0x00, 0xaf, 0x96, 0x00, 0x00, 0xbf, 0x98, 0x00, 0x00, 0xb6, 0x9a, 0x00, 0x00, + 0xa7, 0x30, 0x00, 0x6c, 0x1c, 0x50, 0x00, 0x01, 0x05, 0xd0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, + 0x00, 0x72, 0x00, 0x78, 0x56, 0x74, 0x00, 0x34, 0x12, 0x26, 0x00, 0x00, 0x12, 0x20, 0x00, 0x10, + 0x40, 0x12, 0x00, 0x03, 0x04, 0x02, 0x02, 0x16, 0x21, 0x2c, 0x02, 0x0a, 0x03, 0x2a, 0x01, 0x02, + 0x00, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x32, 0x00, 0x80, 0x00, 0x05, 0x04, 0x5c, 0x00, 0x00, + 0x01, 0x56, 0x00, 0x28, 0x20, 0x58, 0x00, 0x01, 0x00, 0x32, 0x00, 0x24, 0x02, 0x82, 0x00, 0x80, + 0x0c, 0x20, 0x02, 0x88, 0x0d, 0x2a, 0x01, 0x92, 0x07, 0x22, 0x00, 0x01, 0x20, 0x24, 0x00, 0x14, + 0x00, 0x80, 0x00, 0x05, 0x04, 0x5c, 0x00, 0x00, 0x01, 0x56, 0x00, 0x08, 0x20, 0x58, 0x00, 0x03, + 0x00, 0x32, 0x00, 0x08, 0x04, 0x82, 0x00, 0x80, 0x0c, 0x20, 0x02, 0x88, 0x0d, 0x2a, 0x01, 0x18, + 0x04, 0x5c, 0x00, 0x80, 0x00, 0x54, 0x00, 0x00, 0x01, 0x62, 0x00, 0x09, 0x03, 0x64, 0x00, 0x18, + 0x00, 0x82, 0x00, 0x80, 0x0c, 0x20, 0x02, 0x88, 0x0d, 0x2a, 0x01, 0x18, 0x04, 0x5c, 0x00, 0x80, + 0x00, 0x52, 0x00, 0x08, 0x00, 0x54, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x4f, +}; + +/* ---------- OTP custom CRC-8 lookup table ---------- */ +const guint8 goodix53x5_otp_hash_table[256] = { + 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, + 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, + 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, + 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, + 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, + 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, + 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, + 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, + 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, + 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, + 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, + 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, + 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, + 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, + 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, + 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3, +}; + +/* ---------- Message encoding / decoding ---------- */ + +/** + * goodix53x5_encode_message: + * + * USB wire format for the 53x5 family: + * command_byte = (category << 4) | (command << 1) + * frame = [command_byte] [payload_len+1 as u16le] [payload] [checksum] + * + * Chunks are 64 bytes each. First chunk starts with command_byte; continuation + * chunks start with (command_byte | 0x01). Data is zero-padded to 64 bytes. + * + * The function returns a *single* flat buffer of all chunks concatenated. + */ +guint8 * +goodix53x5_encode_message (guint8 category, + guint8 command, + const guint8 *payload, + gsize payload_len, + gsize *out_len) +{ + guint8 command_byte; + guint16 size_field; + guint8 checksum = 0; + GByteArray *frame; + GByteArray *chunks; + guint8 *data; + gsize i; + + g_assert (category <= 0xF); + g_assert (command <= 0x7); + + command_byte = (guint8) ((category << 4) | (command << 1)); + + /* Build the logical frame: [cmd] [size_le16] [payload] [checksum] */ + frame = g_byte_array_new (); + g_byte_array_append (frame, &command_byte, 1); + size_field = GUINT16_TO_LE ((guint16) (payload_len + 1)); + g_byte_array_append (frame, (guint8 *) &size_field, 2); + if (payload_len > 0) + g_byte_array_append (frame, payload, payload_len); + + /* Compute checksum: (0xAA - sum(frame)) & 0xFF */ + checksum = 0xAA; + for (i = 0; i < frame->len; i++) + checksum -= frame->data[i]; + g_byte_array_append (frame, &checksum, 1); + + /* Split into 64-byte chunks */ + chunks = g_byte_array_new (); + + data = frame->data; + i = 0; + + /* First chunk */ + { + gsize chunk_len = MIN ((gsize) GOODIX53X5_USB_CHUNK_SIZE, frame->len); + guint8 chunk[GOODIX53X5_USB_CHUNK_SIZE]; + memset (chunk, 0, sizeof (chunk)); + memcpy (chunk, data, chunk_len); + g_byte_array_append (chunks, chunk, GOODIX53X5_USB_CHUNK_SIZE); + i = chunk_len; + } + + /* Continuation chunks */ + while (i < frame->len) + { + guint8 chunk[GOODIX53X5_USB_CHUNK_SIZE]; + gsize avail = frame->len - i; + gsize chunk_payload = MIN ((gsize) (GOODIX53X5_USB_CHUNK_SIZE - 1), avail); + + memset (chunk, 0, sizeof (chunk)); + chunk[0] = command_byte | 0x01; + memcpy (&chunk[1], data + i, chunk_payload); + g_byte_array_append (chunks, chunk, GOODIX53X5_USB_CHUNK_SIZE); + i += chunk_payload; + } + + *out_len = chunks->len; + data = g_steal_pointer (&chunks->data); + g_byte_array_free (frame, TRUE); + /* Must free the GByteArray struct but NOT the data (already stolen) */ + chunks->data = NULL; + chunks->len = 0; + g_byte_array_free (chunks, FALSE); + + return data; +} + +/** + * goodix53x5_parse_message: + * + * Parse the first complete logical message from a raw USB buffer (which may + * span multiple 64-byte chunks). The buffer is expected to contain exactly + * one full message (with possible trailing zeros from a single bulk read). + */ +gboolean +goodix53x5_parse_message (const guint8 *buf, + gsize buf_len, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len) +{ + guint8 command_byte; + guint16 message_size; + gsize total_needed; + GByteArray *assembled; + guint8 received_checksum; + guint8 computed_checksum; + gsize i; + + if (buf_len < 3) + { + fp_warn ("goodix53x5: message too short (%zu bytes)", buf_len); + return FALSE; + } + + command_byte = buf[0]; + message_size = GUINT16_FROM_LE (*(guint16 *) (buf + 1)); + /* message_size is payload_len + 1 (for checksum); actual payload starts at buf[3] */ + total_needed = 3 + (gsize) message_size; /* cmd + size_field + payload + checksum */ + + fp_dbg ("goodix53x5_parse_message: cmd=0x%02x message_size=%u total_needed=%zu buf_len=%zu", + command_byte, message_size, total_needed, buf_len); + + /* Reassemble from chunks if needed */ + assembled = g_byte_array_new (); + fp_dbg ("goodix53x5_parse_message: appending first chunk: MIN(%zu, %d) = %zu", + buf_len, GOODIX53X5_USB_CHUNK_SIZE, + MIN (buf_len, (gsize) GOODIX53X5_USB_CHUNK_SIZE)); + g_byte_array_append (assembled, buf, (guint) MIN (buf_len, (gsize) GOODIX53X5_USB_CHUNK_SIZE)); + fp_dbg ("goodix53x5_parse_message: after first append, assembled->len=%u", assembled->len); + + { + const guint8 *remaining = buf + GOODIX53X5_USB_CHUNK_SIZE; + gsize remaining_len = (buf_len > GOODIX53X5_USB_CHUNK_SIZE) ? buf_len - GOODIX53X5_USB_CHUNK_SIZE : 0; + + while (assembled->len < total_needed && remaining_len >= GOODIX53X5_USB_CHUNK_SIZE) + { + guint8 contd_cmd = remaining[0]; + if ((contd_cmd & 0x01) == 0 || (contd_cmd & 0xFE) != command_byte) + { + fp_warn ("goodix53x5: bad continuation byte 0x%02x (assembled=%u total=%zu remaining_len=%zu)", + contd_cmd, assembled->len, total_needed, remaining_len); + g_byte_array_free (assembled, TRUE); + return FALSE; + } + fp_dbg ("goodix53x5_parse_message: continuation chunk, assembled=%u, appending 63", assembled->len); + g_byte_array_append (assembled, remaining + 1, GOODIX53X5_USB_CHUNK_SIZE - 1); + remaining += GOODIX53X5_USB_CHUNK_SIZE; + remaining_len -= GOODIX53X5_USB_CHUNK_SIZE; + } + } + + if (assembled->len < total_needed) + { + fp_warn ("goodix53x5: incomplete message: have %u, need %zu", assembled->len, total_needed); + g_byte_array_free (assembled, TRUE); + return FALSE; + } + + /* Verify checksum */ + received_checksum = assembled->data[total_needed - 1]; + if (received_checksum != 0x88) + { + computed_checksum = 0xAA; + for (i = 0; i < total_needed - 1; i++) + computed_checksum -= assembled->data[i]; + if (received_checksum != computed_checksum) + { + fp_warn ("goodix53x5: checksum mismatch: expected 0x%02x, got 0x%02x", + computed_checksum, received_checksum); + g_byte_array_free (assembled, TRUE); + return FALSE; + } + } + + *out_category = command_byte >> 4; + *out_command = (command_byte & 0x0F) >> 1; + *out_payload_len = (gsize) message_size - 1; /* exclude checksum byte from message_size */ + + if (*out_payload_len > 0) + { + *out_payload = g_malloc (*out_payload_len); + memcpy (*out_payload, assembled->data + 3, *out_payload_len); + } + else + { + *out_payload = NULL; + } + + g_byte_array_free (assembled, TRUE); + return TRUE; +} + +/* ---------- OTP parsing ---------- */ + +guint8 +goodix53x5_compute_otp_hash (const guint8 *data, + gsize len) +{ + guint8 checksum = 0; + + for (gsize i = 0; i < len; i++) + checksum = goodix53x5_otp_hash_table[checksum ^ data[i]]; + + return (guint8) (~checksum & 0xFF); +} + +gboolean +goodix53x5_parse_otp (const guint8 *otp, + gsize otp_len, + Goodix53x5CalibParams *calib_params) +{ + guint8 hash_data[64]; + guint8 computed_hash; + guint8 received_hash; + guint8 diff; + guint8 tmp, tmp2; + + if (otp_len < 32) + { + fp_warn ("goodix53x5: OTP too short: %zu bytes", otp_len); + return FALSE; + } + + /* Hash covers otp[0:25] + otp[26:] (i.e., excluding otp[25]) */ + memcpy (hash_data, otp, 25); + memcpy (hash_data + 25, otp + 26, otp_len - 26); + received_hash = otp[25]; + computed_hash = goodix53x5_compute_otp_hash (hash_data, 25 + (otp_len - 26)); + + if (received_hash != computed_hash) + { + fp_warn ("goodix53x5: OTP hash mismatch: expected 0x%02x, got 0x%02x", + computed_hash, received_hash); + return FALSE; + } + + /* tcode */ + calib_params->tcode = (otp[23] != 0) ? (guint16) (otp[23] + 1) : 0; + + /* diff[5:1] from otp[17] */ + diff = (otp[17] >> 1) & 0x1F; + + if (diff == 0) + { + calib_params->delta_fdt = 0; + calib_params->delta_down = 0x0D; + calib_params->delta_up = 0x0B; + calib_params->delta_img = 0xC8; + } + else + { + tmp = (guint8) (diff + 5); + tmp2 = (guint8) ((tmp * 0x32u) >> 4); + calib_params->delta_fdt = tmp2 / 5; + calib_params->delta_down = tmp2 / 3; + calib_params->delta_up = (guint8) (calib_params->delta_down - 2); + calib_params->delta_img = 0xC8; + } + + /* DAC values */ + if (otp[17] == 0 || otp[22] == 0 || otp[31] == 0) + { + calib_params->dac_h = 0x97; + calib_params->dac_l = 0xD0; + } + else + { + calib_params->dac_h = (guint16) (((guint16) otp[17] << 8 ^ (guint16) otp[22]) & 0x1FF); + calib_params->dac_l = (guint16) (((guint16) otp[17] & 0x40u) << 2 ^ (guint16) otp[31]); + } + + memset (calib_params->fdt_base_down, 0, GOODIX53X5_FDT_BASE_LEN); + memset (calib_params->fdt_base_up, 0, GOODIX53X5_FDT_BASE_LEN); + memset (calib_params->fdt_base_manual, 0, GOODIX53X5_FDT_BASE_LEN); + + fp_dbg ("goodix53x5: OTP parsed: tcode=%u, delta_fdt=%u, delta_down=%u, " + "delta_up=%u, dac_h=0x%x, dac_l=0x%x", + calib_params->tcode, calib_params->delta_fdt, + calib_params->delta_down, calib_params->delta_up, + calib_params->dac_h, calib_params->dac_l); + + return TRUE; +} + +/* ---------- Sensor configuration ---------- */ + +static void +replace_value_in_section (guint8 *config, + guint8 section_num, + guint16 tag, + guint16 value) +{ + const guint8 *section_table = config + 1; + guint8 section_base = section_table[section_num * 2]; + guint8 section_size = section_table[section_num * 2 + 1]; + guint8 entry_base; + + for (entry_base = section_base; + entry_base < section_base + section_size; + entry_base += 4) + { + guint16 entry_tag = GUINT16_FROM_LE (*(guint16 *) &config[entry_base]); + if (entry_tag == tag) + { + guint16 val_le = GUINT16_TO_LE (value); + memcpy (&config[entry_base + 2], &val_le, 2); + } + } +} + +static void +fix_config_checksum (guint8 *config) +{ + guint32 checksum = 0xA5A5; + guint16 sum_pair; + guint16 result; + + for (gsize i = 0; i < 256 - 2; i += 2) + { + sum_pair = GUINT16_FROM_LE (*(guint16 *) &config[i]); + checksum = (checksum + sum_pair) & 0xFFFF; + } + result = (guint16) ((0x10000 - checksum) & 0xFFFF); + guint16 result_le = GUINT16_TO_LE (result); + memcpy (&config[254], &result_le, 2); +} + +void +goodix53x5_build_config (const Goodix53x5CalibParams *calib_params, + guint8 *out_config) +{ + memcpy (out_config, goodix53x5_default_config, 256); + + /* Patch TCODE in sections 2, 3, 4 */ + replace_value_in_section (out_config, 2, GOODIX53X5_CFG_TCODE_TAG, calib_params->tcode); + replace_value_in_section (out_config, 3, GOODIX53X5_CFG_TCODE_TAG, calib_params->tcode); + replace_value_in_section (out_config, 4, GOODIX53X5_CFG_TCODE_TAG, calib_params->tcode); + + /* Patch DAC_L in sections 2, 3 */ + replace_value_in_section (out_config, 2, GOODIX53X5_CFG_DAC_L_TAG, + (guint16) ((calib_params->dac_l << 4) | 8)); + replace_value_in_section (out_config, 3, GOODIX53X5_CFG_DAC_L_TAG, + (guint16) ((calib_params->dac_l << 4) | 8)); + + /* Patch DELTA_DOWN in section 2 */ + replace_value_in_section (out_config, 2, GOODIX53X5_CFG_DELTA_DOWN_TAG, + (guint16) ((calib_params->delta_down << 8) | 0x80)); + + fix_config_checksum (out_config); +} + +/* ---------- GTLS session key derivation (TLS 1.2 P_SHA256) ---------- */ + +/* + * HMAC-SHA256 computed using GLib's GChecksum infrastructure. + * Returns heap-allocated 32-byte digest. Caller must g_free(). + */ +static guint8 * +hmac_sha256 (const guint8 *key, + gsize key_len, + const guint8 *data, + gsize data_len) +{ + GHmac *hmac; + guint8 *digest; + gsize digest_len = 32; + + digest = g_malloc (digest_len); + hmac = g_hmac_new (G_CHECKSUM_SHA256, key, key_len); + g_hmac_update (hmac, data, (gssize) data_len); + g_hmac_get_digest (hmac, digest, &digest_len); + g_hmac_unref (hmac); + return digest; +} + +void +goodix53x5_gtls_derive_session_key (const guint8 *psk, + const guint8 *client_random, + const guint8 *server_random, + Goodix53x5GTLSCtx *ctx) +{ + /* seed = "master secret" || client_random || server_random */ + static const guint8 label[] = "master secret"; + guint8 seed[13 + GOODIX53X5_RANDOM_LEN + GOODIX53X5_RANDOM_LEN]; + gsize seed_len = sizeof (seed); + guint8 session_key[GOODIX53X5_GTLS_SESSION_KEY_LEN + 32]; /* extra to avoid re-deriving */ + gsize generated = 0; + guint8 *A; + gsize A_len; + guint8 *A_next; + guint8 *block; + guint8 *A_plus_seed; + + memcpy (seed, label, 13); + memcpy (seed + 13, client_random, GOODIX53X5_RANDOM_LEN); + memcpy (seed + 13 + GOODIX53X5_RANDOM_LEN, server_random, GOODIX53X5_RANDOM_LEN); + + fp_dbg ("goodix53x5: derive_session_key client_random: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + client_random[0], client_random[1], client_random[2], client_random[3], + client_random[4], client_random[5], client_random[6], client_random[7], + client_random[8], client_random[9], client_random[10], client_random[11], + client_random[12], client_random[13], client_random[14], client_random[15], + client_random[16], client_random[17], client_random[18], client_random[19], + client_random[20], client_random[21], client_random[22], client_random[23], + client_random[24], client_random[25], client_random[26], client_random[27], + client_random[28], client_random[29], client_random[30], client_random[31]); + fp_dbg ("goodix53x5: derive_session_key server_random: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + server_random[0], server_random[1], server_random[2], server_random[3], + server_random[4], server_random[5], server_random[6], server_random[7], + server_random[8], server_random[9], server_random[10], server_random[11], + server_random[12], server_random[13], server_random[14], server_random[15], + server_random[16], server_random[17], server_random[18], server_random[19], + server_random[20], server_random[21], server_random[22], server_random[23], + server_random[24], server_random[25], server_random[26], server_random[27], + server_random[28], server_random[29], server_random[30], server_random[31]); + + /* P_SHA256: A(0) = seed (77 bytes); A(i) = HMAC(psk, A(i-1)) which is 32 bytes */ + A = g_malloc (seed_len); + memcpy (A, seed, seed_len); + A_len = seed_len; + + while (generated < GOODIX53X5_GTLS_SESSION_KEY_LEN) + { + /* A(i) = HMAC(psk, A(i-1)) — result is always 32 bytes */ + A_next = hmac_sha256 (psk, GOODIX53X5_PSK_LEN, A, A_len); + g_free (A); + A = A_next; + A_len = 32; /* after first iteration A is always 32 bytes */ + + /* block = HMAC(psk, A || seed) */ + A_plus_seed = g_malloc (A_len + seed_len); + memcpy (A_plus_seed, A, A_len); + memcpy (A_plus_seed + A_len, seed, seed_len); + block = hmac_sha256 (psk, GOODIX53X5_PSK_LEN, A_plus_seed, A_len + seed_len); + g_free (A_plus_seed); + + gsize to_copy = MIN ((gsize) 32, GOODIX53X5_GTLS_SESSION_KEY_LEN - generated); + memcpy (session_key + generated, block, to_copy); + generated += to_copy; + g_free (block); + } + + g_free (A); + + /* Slice the session key into sub-keys */ + memcpy (ctx->symmetric_key, session_key, GOODIX53X5_SYMMETRIC_KEY_LEN); + memcpy (ctx->symmetric_iv, session_key + GOODIX53X5_SYMMETRIC_KEY_LEN, GOODIX53X5_SYMMETRIC_IV_LEN); + memcpy (ctx->hmac_key, session_key + GOODIX53X5_SYMMETRIC_KEY_LEN + GOODIX53X5_SYMMETRIC_IV_LEN, + GOODIX53X5_HMAC_KEY_LEN); + ctx->hmac_client_counter = GUINT16_FROM_LE ( + *(guint16 *) &session_key[GOODIX53X5_SYMMETRIC_KEY_LEN + GOODIX53X5_SYMMETRIC_IV_LEN + GOODIX53X5_HMAC_KEY_LEN]); + ctx->hmac_server_counter = GUINT16_FROM_LE ( + *(guint16 *) &session_key[GOODIX53X5_SYMMETRIC_KEY_LEN + GOODIX53X5_SYMMETRIC_IV_LEN + GOODIX53X5_HMAC_KEY_LEN + 2]); + + fp_dbg ("goodix53x5: session_key symmetric_key: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + ctx->symmetric_key[0], ctx->symmetric_key[1], + ctx->symmetric_key[2], ctx->symmetric_key[3], + ctx->symmetric_key[4], ctx->symmetric_key[5], + ctx->symmetric_key[6], ctx->symmetric_key[7], + ctx->symmetric_key[8], ctx->symmetric_key[9], + ctx->symmetric_key[10], ctx->symmetric_key[11], + ctx->symmetric_key[12], ctx->symmetric_key[13], + ctx->symmetric_key[14], ctx->symmetric_key[15]); + fp_dbg ("goodix53x5: session_key symmetric_iv: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + ctx->symmetric_iv[0], ctx->symmetric_iv[1], + ctx->symmetric_iv[2], ctx->symmetric_iv[3], + ctx->symmetric_iv[4], ctx->symmetric_iv[5], + ctx->symmetric_iv[6], ctx->symmetric_iv[7], + ctx->symmetric_iv[8], ctx->symmetric_iv[9], + ctx->symmetric_iv[10], ctx->symmetric_iv[11], + ctx->symmetric_iv[12], ctx->symmetric_iv[13], + ctx->symmetric_iv[14], ctx->symmetric_iv[15]); + fp_dbg ("goodix53x5: session_key hmac_key: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x ...", + ctx->hmac_key[0], ctx->hmac_key[1], + ctx->hmac_key[2], ctx->hmac_key[3], + ctx->hmac_key[4], ctx->hmac_key[5], + ctx->hmac_key[6], ctx->hmac_key[7], + ctx->hmac_key[8], ctx->hmac_key[9], + ctx->hmac_key[10], ctx->hmac_key[11], + ctx->hmac_key[12], ctx->hmac_key[13], + ctx->hmac_key[14], ctx->hmac_key[15]); + fp_dbg ("goodix53x5: session_key counters: client=0x%04x server=0x%04x", + ctx->hmac_client_counter, ctx->hmac_server_counter); + + /* Dump session key to file for offline debugging */ + { + FILE *f = fopen ("/tmp/goodix_session_key.bin", "wb"); + if (f) + { + fwrite (ctx->symmetric_key, 1, GOODIX53X5_SYMMETRIC_KEY_LEN, f); + fwrite (ctx->symmetric_iv, 1, GOODIX53X5_SYMMETRIC_IV_LEN, f); + fwrite (ctx->hmac_key, 1, GOODIX53X5_HMAC_KEY_LEN, f); + guint16 cc = ctx->hmac_client_counter; + guint16 sc = ctx->hmac_server_counter; + fwrite (&cc, 2, 1, f); + fwrite (&sc, 2, 1, f); + fclose (f); + fp_dbg ("goodix53x5: dumped session key to /tmp/goodix_session_key.bin"); + } + } +} + +void +goodix53x5_gtls_compute_identity (const guint8 *hmac_key, + const guint8 *client_random, + const guint8 *server_random, + guint8 *out_identity) +{ + guint8 data[GOODIX53X5_RANDOM_LEN * 2]; + guint8 *digest; + + memcpy (data, client_random, GOODIX53X5_RANDOM_LEN); + memcpy (data + GOODIX53X5_RANDOM_LEN, server_random, GOODIX53X5_RANDOM_LEN); + digest = hmac_sha256 (hmac_key, GOODIX53X5_HMAC_KEY_LEN, data, sizeof (data)); + memcpy (out_identity, digest, GOODIX53X5_IDENTITY_LEN); + g_free (digest); +} + +/* ---------- AES-128-CBC decryption (via NSS) ---------- */ + +static gboolean +aes128_cbc_decrypt (const guint8 *key, + const guint8 *iv, + const guint8 *ciphertext, + gsize ciphertext_len, + guint8 *plaintext) +{ + PK11SlotInfo *slot = NULL; + PK11SymKey *symkey = NULL; + SECItem iv_item = { siBuffer, (unsigned char *) iv, 16 }; + SECItem *param = NULL; + PK11Context *ctx = NULL; + int outlen = 0; + unsigned int finallen = 0; + gboolean ok = FALSE; + + slot = PK11_GetBestSlot (CKM_AES_CBC, NULL); + if (!slot) + goto out; + + { + SECItem key_item = { siBuffer, (unsigned char *) key, GOODIX53X5_SYMMETRIC_KEY_LEN }; + symkey = PK11_ImportSymKey (slot, CKM_AES_CBC, PK11_OriginUnwrap, + CKA_DECRYPT, &key_item, NULL); + } + if (!symkey) + goto out; + + param = PK11_ParamFromIV (CKM_AES_CBC, &iv_item); + if (!param) + goto out; + + ctx = PK11_CreateContextBySymKey (CKM_AES_CBC, CKA_DECRYPT, symkey, param); + if (!ctx) + goto out; + + if (PK11_CipherOp (ctx, plaintext, &outlen, (int) ciphertext_len, + (unsigned char *) ciphertext, (int) ciphertext_len) != SECSuccess) + goto out; + + if (PK11_DigestFinal (ctx, plaintext + outlen, &finallen, + (unsigned int) ciphertext_len - (unsigned int) outlen) != SECSuccess) + goto out; + + ok = TRUE; + +out: + if (ctx) PK11_DestroyContext (ctx, PR_TRUE); + if (param) SECITEM_FreeItem (param, PR_TRUE); + if (symkey) PK11_FreeSymKey (symkey); + if (slot) PK11_FreeSlot (slot); + return ok; +} + +/* ---------- GEA stream cipher decryption ---------- */ + +static void +gea_decrypt (const guint8 *gea_key_bytes, + const guint8 *encrypted, + gsize encrypted_len, + guint8 *decrypted) +{ + guint32 key = GUINT32_FROM_LE (*(guint32 *) gea_key_bytes); + gsize idx; + + for (idx = 0; idx + 1 < encrypted_len; idx += 2) + { + guint32 uVar3, uVar2, uVar1; + guint16 input_element, stream_val, result; + + uVar3 = (key >> 1 ^ key) & 0xFFFFFFFF; + uVar2 = ((((((( + (((key >> 0xF) & 0x2000) | (key & 0x1000000)) >> 1 | (key & 0x20000)) >> 2 + | (key & 0x1000)) >> 3 | ((key >> 7 ^ key) & 0x80000)) >> 1 | + ((key >> 0xF ^ key) & 0x4000)) >> 2 | (key & 0x2000)) >> 2 + | (uVar3 & 0x40) | (key & 0x20)) >> 1 | + ((key >> 9 ^ key << 8) & 0x800) | ((key >> 0x14 ^ key * 2) & 4) | + ((key * 8 ^ key >> 0x10) & 0x4000) | + ((key >> 2 ^ key >> 0x10) & 0x80) | + ((key << 6 ^ key >> 7) & 0x100) | ((key & 0x100) << 7)); + uVar2 &= 0xFFFFFFFF; + uVar1 = key & 0xFFFF; + + key = (guint32) (((key ^ (uVar3 >> 0x14 ^ key) >> 10) << 0x1F | key >> 1) & 0xFFFFFFFF); + + input_element = GUINT16_FROM_LE (*(guint16 *) (encrypted + idx)); + stream_val = (guint16) ((((uVar2 >> 8) & 0xFFFF) + (guint16) (((uVar2 & 0xFF) | (uVar1 & 1)) * 0x100)) & 0xFFFF); + result = GUINT16_TO_LE ((guint16) (input_element ^ stream_val)); + memcpy (decrypted + idx, &result, 2); + } + + /* Handle odd byte if any (just copy it) */ + if (encrypted_len % 2 != 0) + decrypted[encrypted_len - 1] = encrypted[encrypted_len - 1]; +} + +/* ---------- GTLS image decryption ---------- */ + +gboolean +goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, + const guint8 *encrypted_msg, + gsize encrypted_msg_len, + guint8 **out_raw, + gsize *out_raw_len) +{ + guint32 data_type; + guint32 msg_length; + const guint8 *payload; + gsize payload_len; + const guint8 *payload_hmac; + GByteArray *gea_data; + guint8 hmac_input[4 + 0x400]; + guint8 *hmac_digest; + guint8 *gea_decrypted; + guint32 reported_crc; + guint32 computed_crc; + + if (encrypted_msg_len < 8 + 32) + { + fp_warn ("goodix53x5: encrypted message too short"); + return FALSE; + } + + fp_dbg ("goodix53x5: decrypt_image: encrypted_msg_len=%zu, first 8 bytes: %02x %02x %02x %02x %02x %02x %02x %02x", + encrypted_msg_len, + encrypted_msg[0], encrypted_msg[1], encrypted_msg[2], encrypted_msg[3], + encrypted_msg[4], encrypted_msg[5], encrypted_msg[6], encrypted_msg[7]); + + /* Dump raw encrypted image to file for offline debugging */ + { + FILE *f = fopen ("/tmp/goodix_encrypted_img.bin", "wb"); + if (f) + { + fwrite (encrypted_msg, 1, encrypted_msg_len, f); + fclose (f); + fp_dbg ("goodix53x5: dumped %zu bytes to /tmp/goodix_encrypted_img.bin", encrypted_msg_len); + } + } + + data_type = GUINT32_FROM_LE (*(guint32 *) encrypted_msg); + if (data_type != GOODIX53X5_IMAGE_DATA_TYPE) + { + fp_warn ("goodix53x5: unexpected image data type: 0x%08x", data_type); + return FALSE; + } + + msg_length = GUINT32_FROM_LE (*(guint32 *) (encrypted_msg + 4)); + if (msg_length != (guint32) encrypted_msg_len) + { + fp_warn ("goodix53x5: image length mismatch: reported %u, actual %zu", + msg_length, encrypted_msg_len); + return FALSE; + } + + /* Layout: [8 bytes header] [encrypted payload] [32 bytes HMAC] */ + payload = encrypted_msg + 8; + payload_len = encrypted_msg_len - 8 - 32; + payload_hmac = encrypted_msg + encrypted_msg_len - 32; + + /* Reassemble GEA-encrypted data from 15 interleaved blocks: + * even blocks (0,2,4,...14): GEA-encrypted (raw/untouched here) + * odd blocks (1,3,5,...13): AES-CBC encrypted → decrypt into gea buffer + * + * We build gea_data as the concatenation of: + * - even blocks as-is + * - odd blocks AES-CBC decrypted (decrypted bytes go in the same positions) + * + * The reference places decrypted AES blocks directly into the gea buffer, + * interleaved with the GEA blocks. The HMAC is computed over the last 0x400 + * bytes of gea_data. + */ + gea_data = g_byte_array_new (); + + { + const guint8 *p = payload; + guint8 aes_plain[GOODIX53X5_AES_BLOCK_SIZE]; + + for (guint block_idx = 0; block_idx < GOODIX53X5_ENCRYPT_BLOCKS; block_idx++) + { + if (block_idx % 2 == 0) + { + /* GEA block (appended as-is) */ + gsize block_size; + if (block_idx == 0) + block_size = GOODIX53X5_GEA_FIRST_BLOCK_SIZE; + else if (block_idx == GOODIX53X5_ENCRYPT_BLOCKS - 1) + { + /* Last even block: take remaining payload. + * Before block 14 we have consumed: + * block 0 → GEA_FIRST_BLOCK_SIZE + * blocks 2,4,6,8,10,12 (6 blocks) → 6 * GEA_BLOCK_SIZE + * blocks 1,3,5,7,9,11,13 (7 blocks) → 7 * AES_BLOCK_SIZE + * Note: ENCRYPT_BLOCKS/2 == 7, which is the number of odd blocks, + * but only 6 non-first even blocks precede block 14. */ + gsize used = GOODIX53X5_GEA_FIRST_BLOCK_SIZE + + ((GOODIX53X5_ENCRYPT_BLOCKS / 2) - 1) * GOODIX53X5_GEA_BLOCK_SIZE + + (GOODIX53X5_ENCRYPT_BLOCKS / 2) * GOODIX53X5_AES_BLOCK_SIZE; + block_size = payload_len - used; + } + else + block_size = GOODIX53X5_GEA_BLOCK_SIZE; + + g_byte_array_append (gea_data, p, (guint) block_size); + p += block_size; + } + else + { + /* AES-CBC block: decrypt and append */ + memset (aes_plain, 0, sizeof (aes_plain)); + if (!aes128_cbc_decrypt (ctx->symmetric_key, ctx->symmetric_iv, + p, GOODIX53X5_AES_BLOCK_SIZE, aes_plain)) + { + fp_warn ("goodix53x5: AES decryption failed at block %u", block_idx); + g_byte_array_free (gea_data, TRUE); + return FALSE; + } + if (block_idx == 1) + { + fp_dbg ("goodix53x5: AES block1 cipher[0..15]: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + p[0], p[1], p[2], p[3], + p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], + p[12], p[13], p[14], p[15]); + fp_dbg ("goodix53x5: AES block1 plain[0..15]: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + aes_plain[0], aes_plain[1], aes_plain[2], aes_plain[3], + aes_plain[4], aes_plain[5], aes_plain[6], aes_plain[7], + aes_plain[8], aes_plain[9], aes_plain[10], aes_plain[11], + aes_plain[12], aes_plain[13], aes_plain[14], aes_plain[15]); + } + g_byte_array_append (gea_data, aes_plain, GOODIX53X5_AES_BLOCK_SIZE); + p += GOODIX53X5_AES_BLOCK_SIZE; + } + } + } + + /* Verify HMAC-SHA256 over (hmac_server_counter_le32 || gea_data[-0x400:]) */ + { + gsize gea_len = gea_data->len; + gsize tail_off = (gea_len >= 0x400) ? gea_len - 0x400 : 0; + gsize tail_len = gea_len - tail_off; + guint32 counter_le = GUINT32_TO_LE ((guint32) ctx->hmac_server_counter); + + memcpy (hmac_input, &counter_le, 4); + memcpy (hmac_input + 4, gea_data->data + tail_off, tail_len); + + hmac_digest = hmac_sha256 (ctx->hmac_key, GOODIX53X5_HMAC_KEY_LEN, + hmac_input, 4 + tail_len); + + fp_dbg ("goodix53x5: HMAC counter=0x%04x gea_len=%zu tail_off=%zu tail_len=%zu", + ctx->hmac_server_counter, gea_len, tail_off, tail_len); + fp_dbg ("goodix53x5: HMAC expected: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + payload_hmac[0], payload_hmac[1], payload_hmac[2], payload_hmac[3], + payload_hmac[4], payload_hmac[5], payload_hmac[6], payload_hmac[7], + payload_hmac[8], payload_hmac[9], payload_hmac[10], payload_hmac[11], + payload_hmac[12], payload_hmac[13], payload_hmac[14], payload_hmac[15], + payload_hmac[16], payload_hmac[17], payload_hmac[18], payload_hmac[19], + payload_hmac[20], payload_hmac[21], payload_hmac[22], payload_hmac[23], + payload_hmac[24], payload_hmac[25], payload_hmac[26], payload_hmac[27], + payload_hmac[28], payload_hmac[29], payload_hmac[30], payload_hmac[31]); + fp_dbg ("goodix53x5: HMAC computed: " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + hmac_digest[0], hmac_digest[1], hmac_digest[2], hmac_digest[3], + hmac_digest[4], hmac_digest[5], hmac_digest[6], hmac_digest[7], + hmac_digest[8], hmac_digest[9], hmac_digest[10], hmac_digest[11], + hmac_digest[12], hmac_digest[13], hmac_digest[14], hmac_digest[15], + hmac_digest[16], hmac_digest[17], hmac_digest[18], hmac_digest[19], + hmac_digest[20], hmac_digest[21], hmac_digest[22], hmac_digest[23], + hmac_digest[24], hmac_digest[25], hmac_digest[26], hmac_digest[27], + hmac_digest[28], hmac_digest[29], hmac_digest[30], hmac_digest[31]); + fp_dbg ("goodix53x5: HMAC key: %02x %02x %02x %02x ...", + ctx->hmac_key[0], ctx->hmac_key[1], ctx->hmac_key[2], ctx->hmac_key[3]); + fp_dbg ("goodix53x5: HMAC input[0..7]: %02x %02x %02x %02x %02x %02x %02x %02x", + hmac_input[0], hmac_input[1], hmac_input[2], hmac_input[3], + hmac_input[4], hmac_input[5], hmac_input[6], hmac_input[7]); + + gboolean hmac_ok = (memcmp (hmac_digest, payload_hmac, 32) == 0); + g_free (hmac_digest); + + /* Always advance the server counter: the device increments its own counter + * for every image it sends, regardless of whether we verify it. */ + ctx->hmac_server_counter = (guint16) ((ctx->hmac_server_counter + 1) & 0xFFFF); + + if (!hmac_ok) + { + fp_warn ("goodix53x5: HMAC verification failed"); + g_byte_array_free (gea_data, TRUE); + return FALSE; + } + } + + /* Discard first 5 bytes (alignment padding per reference impl) */ + if (gea_data->len < 5 + 4) + { + fp_warn ("goodix53x5: GEA data too short after trim"); + g_byte_array_free (gea_data, TRUE); + return FALSE; + } + { + guint8 *gea_buf = gea_data->data + 5; + gsize gea_len = gea_data->len - 5; + + /* Last 4 bytes are CRC32/MPEG-2 of the data before them */ + if (gea_len < 4) + { + fp_warn ("goodix53x5: GEA data too short for CRC"); + g_byte_array_free (gea_data, TRUE); + return FALSE; + } + gea_len -= 4; + /* CRC is stored in the custom decode_u32 byte ordering from the reference + * implementation: bytes [b0,b1,b2,b3] → value = b2<<24 | b3<<16 | b0<<8 | b1 + * (each 16-bit word is byte-swapped, then the two words are big-endian). */ + reported_crc = ((guint32) gea_buf[gea_len + 2] << 24) + | ((guint32) gea_buf[gea_len + 3] << 16) + | ((guint32) gea_buf[gea_len + 0] << 8) + | (guint32) gea_buf[gea_len + 1]; + + /* CRC32/MPEG-2 = CRC32 with initial value 0xFFFFFFFF, no final XOR, + * bit-reversed poly 0x04C11DB7. + * Computed manually (same as Python crccheck.crc.Crc32Mpeg2). */ + { + guint32 crc = 0xFFFFFFFF; + for (gsize i = 0; i < gea_len; i++) + { + crc ^= (guint32) gea_buf[i] << 24; + for (guint b = 0; b < 8; b++) + crc = (crc & 0x80000000) ? (crc << 1) ^ 0x04C11DB7 : (crc << 1); + } + computed_crc = crc; + } + + if (reported_crc != computed_crc) + { + fp_warn ("goodix53x5: GEA CRC mismatch: reported 0x%08x, computed 0x%08x", + reported_crc, computed_crc); + g_byte_array_free (gea_data, TRUE); + return FALSE; + } + + /* Decrypt GEA data */ + gea_decrypted = g_malloc (gea_len); + gea_decrypt (ctx->symmetric_key, gea_buf, gea_len, gea_decrypted); + + *out_raw = gea_decrypted; + *out_raw_len = gea_len; + } + + g_byte_array_free (gea_data, TRUE); + return TRUE; +} + +/* ---------- Image decoding (12-bit packed pixels) ---------- */ + +gboolean +goodix53x5_decode_image (const guint8 *raw, + gsize raw_len, + guint16 **out_pixels) +{ + gsize n_pixels; + guint16 *pixels; + gsize src_idx; + gsize dst_idx; + + /* 6 bytes → 4 pixels */ + if (raw_len < 6 || (raw_len % 6) != 0) + { + fp_warn ("goodix53x5: raw data length %zu not a multiple of 6", raw_len); + return FALSE; + } + + n_pixels = (raw_len / 6) * 4; + + if (n_pixels != GOODIX53X5_SENSOR_PIXELS) + { + fp_warn ("goodix53x5: unexpected pixel count: %zu (expected %u)", + n_pixels, GOODIX53X5_SENSOR_PIXELS); + return FALSE; + } + + pixels = g_malloc (GOODIX53X5_SENSOR_PIXELS * sizeof (guint16)); + + src_idx = 0; + dst_idx = 0; + while (src_idx + 5 < raw_len) + { + const guint8 *c = raw + src_idx; + pixels[dst_idx + 0] = (guint16) (((guint16) (c[0] & 0x0F) << 8) | c[1]); + pixels[dst_idx + 1] = (guint16) (((guint16) c[3] << 4) | (c[0] >> 4)); + pixels[dst_idx + 2] = (guint16) (((guint16) (c[5] & 0x0F) << 8) | c[2]); + pixels[dst_idx + 3] = (guint16) (((guint16) c[4] << 4) | (c[5] >> 4)); + src_idx += 6; + dst_idx += 4; + } + + *out_pixels = pixels; + return TRUE; +} + +/* ---------- FDT base helpers ---------- */ + +void +goodix53x5_generate_fdt_base (const guint8 *fdt_data, + guint8 *out_fdt_base) +{ + for (gsize i = 0; i < GOODIX53X5_FDT_BASE_LEN; i += 2) + { + guint16 fdt_val = GUINT16_FROM_LE (*(guint16 *) (fdt_data + i)); + guint32 base_val = (guint32) ((fdt_val & 0xFFFEu) * 0x80u | (fdt_val >> 1)); + guint16 base_val16 = GUINT16_TO_LE ((guint16) (base_val & 0xFFFF)); + memcpy (out_fdt_base + i, &base_val16, 2); + } +} + +void +goodix53x5_generate_fdt_up_base (const guint8 *fdt_data, + guint16 touch_flag, + const Goodix53x5CalibParams *calib_params, + guint8 *out_fdt_base_up) +{ + guint16 fdt_vals[12]; + guint16 fdt_base_up_vals[12]; + + for (guint i = 0; i < 12; i++) + fdt_vals[i] = GUINT16_FROM_LE (*(guint16 *) (fdt_data + i * 2)); + + for (guint i = 0; i < 12; i++) + { + guint16 val = (guint16) ((fdt_vals[i] >> 1) + calib_params->delta_down); + fdt_base_up_vals[i] = (guint16) (val * 0x100u | val); + } + + for (guint i = 0; i < 12; i++) + { + if (((touch_flag >> i) & 1) == 0) + { + guint16 du = calib_params->delta_up; + fdt_base_up_vals[i] = (guint16) (du * 0x100u | du); + } + } + + for (guint i = 0; i < 12; i++) + { + guint16 le = GUINT16_TO_LE (fdt_base_up_vals[i]); + memcpy (out_fdt_base_up + i * 2, &le, 2); + } +} + +gboolean +goodix53x5_is_fdt_base_valid (const guint8 *fdt_data_1, + const guint8 *fdt_data_2, + guint8 max_delta) +{ + for (gsize i = 0; i < GOODIX53X5_FDT_BASE_LEN; i += 2) + { + guint16 v1 = GUINT16_FROM_LE (*(guint16 *) (fdt_data_1 + i)); + guint16 v2 = GUINT16_FROM_LE (*(guint16 *) (fdt_data_2 + i)); + gint delta = (gint) (v2 >> 1) - (gint) (v1 >> 1); + if (delta < 0) + delta = -delta; + if (delta > (gint) max_delta) + return FALSE; + } + return TRUE; +} diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.h b/libfprint/drivers/goodix53x5/goodix53x5_proto.h new file mode 100644 index 00000000..aaba4c83 --- /dev/null +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.h @@ -0,0 +1,285 @@ +/* + * Goodix 53x5 series driver for libfprint + * Copyright (C) 2024 libfprint contributors + * + * Based on the reference Python implementation by goodix-fp-dump contributors + * https://github.com/nickel-org/goodix-fp-dump + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include +#include +#include +#include + +/* USB transport constants */ +#define GOODIX53X5_USB_CHUNK_SIZE 0x40 /* 64 bytes per USB bulk chunk */ + +/* Sensor image dimensions */ +#define GOODIX53X5_SENSOR_WIDTH 108 +#define GOODIX53X5_SENSOR_HEIGHT 88 +#define GOODIX53X5_SENSOR_PIXELS (GOODIX53X5_SENSOR_WIDTH * GOODIX53X5_SENSOR_HEIGHT) + +/* FDT base length (12 × 2-byte values = 24 bytes) */ +#define GOODIX53X5_FDT_BASE_LEN 24 + +/* GTLS session key length */ +#define GOODIX53X5_GTLS_SESSION_KEY_LEN 0x44 /* 68 bytes */ +#define GOODIX53X5_SYMMETRIC_KEY_LEN 16 +#define GOODIX53X5_SYMMETRIC_IV_LEN 16 +#define GOODIX53X5_HMAC_KEY_LEN 32 +#define GOODIX53X5_RANDOM_LEN 32 +#define GOODIX53X5_IDENTITY_LEN 32 +#define GOODIX53X5_PSK_LEN 32 +#define GOODIX53X5_PSK_WHITE_BOX_LEN 96 + +/* Encrypted image data type tag */ +#define GOODIX53X5_IMAGE_DATA_TYPE 0xAA01u + +/* Number of interleaved encryption blocks in image payload */ +#define GOODIX53X5_ENCRYPT_BLOCKS 15 +/* Size of even (GEA) blocks after block 0 */ +#define GOODIX53X5_GEA_BLOCK_SIZE 0x3F0 +/* Size of first GEA block (smaller) */ +#define GOODIX53X5_GEA_FIRST_BLOCK_SIZE 0x3A7 +/* Size of each AES-CBC block */ +#define GOODIX53X5_AES_BLOCK_SIZE 0x3F0 + +/* GTLS MCU message types */ +#define GOODIX53X5_MCU_CLIENT_HELLO 0xFF01u +#define GOODIX53X5_MCU_SERVER_IDENTITY 0xFF02u +#define GOODIX53X5_MCU_CLIENT_IDENTITY 0xFF03u +#define GOODIX53X5_MCU_SERVER_DONE 0xFF04u + +/* Production read/write types */ +#define GOODIX53X5_PROD_SEALED_PSK 0xB001u +#define GOODIX53X5_PROD_PSK_WHITE_BOX 0xB002u +#define GOODIX53X5_PROD_PSK_HASH 0xB003u + +/* Message categories */ +#define GOODIX53X5_CAT_IMAGE 0x2 +#define GOODIX53X5_CAT_FDT 0x3 +#define GOODIX53X5_CAT_SLEEP 0x6 +#define GOODIX53X5_CAT_READ_REG 0x8 +#define GOODIX53X5_CAT_CONFIG 0x9 +#define GOODIX53X5_CAT_RESET 0xA +#define GOODIX53X5_CAT_ACK 0xB +#define GOODIX53X5_CAT_MCU 0xD +#define GOODIX53X5_CAT_PRODUCTION 0xE + +/* Message commands within categories */ +#define GOODIX53X5_CMD_IMAGE_GET 0x0 +#define GOODIX53X5_CMD_FDT_DOWN 0x1 +#define GOODIX53X5_CMD_FDT_UP 0x2 +#define GOODIX53X5_CMD_FDT_MANUAL 0x3 +#define GOODIX53X5_CMD_SLEEP 0x0 +#define GOODIX53X5_CMD_READ_REG 0x1 +#define GOODIX53X5_CMD_CONFIG_UPLOAD 0x0 +#define GOODIX53X5_CMD_RESET 0x1 +#define GOODIX53X5_CMD_READ_OTP 0x3 +#define GOODIX53X5_CMD_FW_VERSION 0x4 +#define GOODIX53X5_CMD_EC_CONTROL 0x7 +#define GOODIX53X5_CMD_MCU 0x1 +#define GOODIX53X5_CMD_PROD_WRITE 0x1 +#define GOODIX53X5_CMD_PROD_READ 0x2 + +/* FDT operation opcode bytes */ +#define GOODIX53X5_FDT_DOWN_OPCODE 0x0C +#define GOODIX53X5_FDT_UP_OPCODE 0x0E +/* Manual FDT opcodes: 0x0D (TX on), 0x8D (TX off) */ +#define GOODIX53X5_FDT_MANUAL_TX_ON 0x0D +#define GOODIX53X5_FDT_MANUAL_TX_OFF 0x8D + +/* Image capture opcode flags */ +#define GOODIX53X5_IMG_TX_ENABLE 0x01 +#define GOODIX53X5_IMG_TX_DISABLE 0x81 +#define GOODIX53X5_IMG_FINGER_FLAG 0x40 +#define GOODIX53X5_IMG_HV_VALUE 0x06 +#define GOODIX53X5_IMG_HV_DISABLED 0x10 + +/* Default sensor config (256 bytes) - from reference implementation */ +extern const guint8 goodix53x5_default_config[256]; + +/* Config register tags */ +#define GOODIX53X5_CFG_TCODE_TAG 0x005Cu +#define GOODIX53X5_CFG_DAC_L_TAG 0x0220u +#define GOODIX53X5_CFG_DELTA_DOWN_TAG 0x0082u + +/* OTP custom CRC-8 table (256 bytes) */ +extern const guint8 goodix53x5_otp_hash_table[256]; + +/* + * Calibration parameters derived from OTP + */ +typedef struct +{ + guint16 tcode; + guint16 dac_h; + guint16 dac_l; + + guint8 delta_fdt; + guint8 delta_down; + guint8 delta_up; + guint16 delta_img; + + guint8 fdt_base_down[GOODIX53X5_FDT_BASE_LEN]; + guint8 fdt_base_up[GOODIX53X5_FDT_BASE_LEN]; + guint8 fdt_base_manual[GOODIX53X5_FDT_BASE_LEN]; +} Goodix53x5CalibParams; + +/* + * GTLS (Goodix TLS) session context + */ +typedef struct +{ + guint8 psk[GOODIX53X5_PSK_LEN]; + guint8 client_random[GOODIX53X5_RANDOM_LEN]; + guint8 server_random[GOODIX53X5_RANDOM_LEN]; + guint8 symmetric_key[GOODIX53X5_SYMMETRIC_KEY_LEN]; + guint8 symmetric_iv[GOODIX53X5_SYMMETRIC_IV_LEN]; + guint8 hmac_key[GOODIX53X5_HMAC_KEY_LEN]; + guint16 hmac_client_counter; + guint16 hmac_server_counter; + gboolean connected; +} Goodix53x5GTLSCtx; + +/* ---- Protocol utility functions ---- */ + +/** + * goodix53x5_encode_message: + * Encode a message into the USB wire format. + * Returns heap-allocated buffer + sets @out_len. + * Caller must g_free(). + */ +guint8 * goodix53x5_encode_message (guint8 category, + guint8 command, + const guint8 *payload, + gsize payload_len, + gsize *out_len); + +/** + * goodix53x5_parse_message: + * Parse the first complete message from @buf / @buf_len. + * Returns TRUE on success. Decoded category/command/payload returned in out params. + * @out_payload is heap-allocated, caller must g_free(). + */ +gboolean goodix53x5_parse_message (const guint8 *buf, + gsize buf_len, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len); + +/** + * goodix53x5_compute_otp_hash: + * Compute the custom CRC-8 over @data of @len bytes. + */ +guint8 goodix53x5_compute_otp_hash (const guint8 *data, + gsize len); + +/** + * goodix53x5_parse_otp: + * Parse OTP data and fill @calib_params. + * Returns TRUE on success. + */ +gboolean goodix53x5_parse_otp (const guint8 *otp, + gsize otp_len, + Goodix53x5CalibParams *calib_params); + +/** + * goodix53x5_build_config: + * Build a patched config blob from DEFAULT_CONFIG + calibration params. + * @out_config must be 256 bytes. + */ +void goodix53x5_build_config (const Goodix53x5CalibParams *calib_params, + guint8 *out_config); + +/** + * goodix53x5_gtls_derive_session_key: + * Derive the GTLS session key using HMAC-SHA256 PRF (TLS 1.2-style). + * @psk: 32-byte pre-shared key + * @client_random: 32-byte client random + * @server_random: 32-byte server random + * @ctx: output context to fill with derived key material + */ +void goodix53x5_gtls_derive_session_key (const guint8 *psk, + const guint8 *client_random, + const guint8 *server_random, + Goodix53x5GTLSCtx *ctx); + +/** + * goodix53x5_gtls_compute_identity: + * Compute HMAC-SHA256(hmac_key, client_random || server_random). + * @out_identity must be GOODIX53X5_IDENTITY_LEN bytes. + */ +void goodix53x5_gtls_compute_identity (const guint8 *hmac_key, + const guint8 *client_random, + const guint8 *server_random, + guint8 *out_identity); + +/** + * goodix53x5_gtls_decrypt_image: + * Decrypt the encrypted image payload using the GTLS context. + * @ctx: must be in connected state + * @encrypted_msg: full encrypted message blob from device + * @encrypted_msg_len: length of @encrypted_msg + * @out_raw: heap-allocated raw (GEA-decrypted) pixel data, caller must g_free() + * @out_raw_len: length of @out_raw + * Returns TRUE on success. + */ +gboolean goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, + const guint8 *encrypted_msg, + gsize encrypted_msg_len, + guint8 **out_raw, + gsize *out_raw_len); + +/** + * goodix53x5_decode_image: + * Decode 12-bit packed pixel data into an array of guint16 pixel values. + * @raw: input raw bytes (6 bytes → 4 pixels) + * @raw_len: length of @raw + * @out_pixels: heap-allocated array of GOODIX53X5_SENSOR_PIXELS guint16, caller must g_free() + * Returns TRUE on success. + */ +gboolean goodix53x5_decode_image (const guint8 *raw, + gsize raw_len, + guint16 **out_pixels); + +/** + * goodix53x5_update_fdt_base: + * Convert FDT event data into calibrated FDT base values. + */ +void goodix53x5_generate_fdt_base (const guint8 *fdt_data, + guint8 *out_fdt_base); + +/** + * goodix53x5_generate_fdt_up_base: + * Generate fdt_base_up from touch event fdt_data and calibration params. + */ +void goodix53x5_generate_fdt_up_base (const guint8 *fdt_data, + guint16 touch_flag, + const Goodix53x5CalibParams *calib_params, + guint8 *out_fdt_base_up); + +/** + * goodix53x5_is_fdt_base_valid: + * Check whether two FDT data snapshots are within @max_delta of each other. + */ +gboolean goodix53x5_is_fdt_base_valid (const guint8 *fdt_data_1, + const guint8 *fdt_data_2, + guint8 max_delta); diff --git a/libfprint/meson.build b/libfprint/meson.build index d3c8b034..2c62f368 100644 --- a/libfprint/meson.build +++ b/libfprint/meson.build @@ -139,6 +139,8 @@ driver_sources = { [ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ], 'goodixmoc' : [ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ], + 'goodix53x5' : + [ 'drivers/goodix53x5/goodix53x5.c', 'drivers/goodix53x5/goodix53x5_proto.c' ], 'fpcmoc' : [ 'drivers/fpcmoc/fpc.c' ], } diff --git a/meson.build b/meson.build index 1badb164..e5e229bc 100644 --- a/meson.build +++ b/meson.build @@ -124,6 +124,7 @@ default_drivers = [ 'upeksonly', 'upekts', 'goodixmoc', + 'goodix53x5', 'nb1010', 'fpcmoc', @@ -157,6 +158,7 @@ driver_helper_mapping = { 'aes3500' : [ 'aeslib', 'aes3k' ], 'aes4000' : [ 'aeslib', 'aes3k' ], 'uru4000' : [ 'nss' ], + 'goodix53x5' : [ 'nss' ], 'elanspi' : [ 'udev' ], 'virtual_image' : [ 'virtual' ], 'virtual_device' : [ 'virtual' ], From b0157a6b2f23a8b55001d453f8ac7919be4ccc0b Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Mon, 6 Apr 2026 15:09:01 +0200 Subject: [PATCH 2/6] goodix53x5: fix upscaling, capture race, and clean up debug logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Upscale captured image 2× via fpi_image_resize so MINDTCT finds enough minutiae for Bozorth3 matching (≥10 required); set img_width and img_height to scaled dimensions; add GOODIX53X5_ENLARGE_FACTOR=2 to proto header - Add 'aes3k' helper to driver_helper_mapping so pixman-1 is linked (required by fpi_image_resize) - Fix capture-loop race: report finger-off status in capture_ssm_done() after SSM teardown, not from inside the CAPTURE_EC_OFF_PARSE state handler, to avoid starting a new SSM before fpi_ssm_next_state() returns - Remove /tmp file dumps of session key and encrypted image (security), remove verbose per-frame hex dumps of crypto material; keep concise per-message and per-handshake debug lines --- libfprint/drivers/goodix53x5/goodix53x5.c | 31 +++- .../drivers/goodix53x5/goodix53x5_proto.c | 159 +----------------- .../drivers/goodix53x5/goodix53x5_proto.h | 6 + meson.build | 2 +- 4 files changed, 38 insertions(+), 160 deletions(-) diff --git a/libfprint/drivers/goodix53x5/goodix53x5.c b/libfprint/drivers/goodix53x5/goodix53x5.c index 688361c9..ea47f6de 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5.c +++ b/libfprint/drivers/goodix53x5/goodix53x5.c @@ -1331,8 +1331,16 @@ capture_ssm_done (FpiSsm *ssm, return; } - /* Start another capture cycle */ - goodix53x5_start_capture_ssm (idev); + /* Report finger-off here, after the SSM is fully torn down (self->ssm = NULL). + * This causes the framework to transition AWAIT_FINGER_OFF → IDLE → + * AWAIT_FINGER_ON, which calls our change_state() vfunc → + * goodix53x5_start_capture_ssm() for the next cycle. + * + * We must NOT do this from inside a SSM state handler (e.g. EC_OFF_PARSE) + * because change_state() is called synchronously by the framework and would + * start a new SSM before fpi_ssm_next_state() returns to complete the old + * one, causing an 'machine != NULL' assertion failure. */ + fpi_image_device_report_finger_status (idev, FALSE); } static void @@ -1549,7 +1557,14 @@ capture_ssm_state (FpiSsm *ssm, img->data[i] = (guint8) (pixels[i] >> 4); /* 12→8 bit */ g_free (pixels); - fpi_image_device_image_captured (idev, img); + /* Upscale: the raw 108×88 sensor gives MINDTCT too few minutiae + * (typically <10, below Bozorth3's minimum of 10). 2× bilinear + * resize gives 216×176 which yields enough minutiae for matching. */ + FpImage *scaled = fpi_image_resize (img, GOODIX53X5_ENLARGE_FACTOR, + GOODIX53X5_ENLARGE_FACTOR); + g_object_unref (img); + + fpi_image_device_image_captured (idev, scaled); fpi_ssm_next_state (ssm); } break; @@ -1595,7 +1610,11 @@ capture_ssm_state (FpiSsm *ssm, } g_free (payload); - fpi_image_device_report_finger_status (idev, FALSE); + /* Do NOT report finger-off yet: we still need to send SLEEP and + * EC_OFF before the SSM completes. Reporting here causes libfprint + * to restart the capture SSM immediately, creating a race where the + * sleep ACK is consumed by the new cycle's EC-ON recv. We report + * finger-off at the end of CAPTURE_EC_OFF_PARSE instead. */ fpi_ssm_next_state (ssm); } break; @@ -1804,8 +1823,8 @@ fpi_device_goodix53x5_class_init (FpiDeviceGoodix53x5Class *klass) dev_class->id_table = id_table; dev_class->scan_type = FP_SCAN_TYPE_PRESS; - img_class->img_width = GOODIX53X5_SENSOR_WIDTH; - img_class->img_height = GOODIX53X5_SENSOR_HEIGHT; + img_class->img_width = GOODIX53X5_SENSOR_WIDTH * GOODIX53X5_ENLARGE_FACTOR; + img_class->img_height = GOODIX53X5_SENSOR_HEIGHT * GOODIX53X5_ENLARGE_FACTOR; img_class->bz3_threshold = 24; diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.c b/libfprint/drivers/goodix53x5/goodix53x5_proto.c index 48644efe..4db047b0 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.c +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.c @@ -192,16 +192,11 @@ goodix53x5_parse_message (const guint8 *buf, /* message_size is payload_len + 1 (for checksum); actual payload starts at buf[3] */ total_needed = 3 + (gsize) message_size; /* cmd + size_field + payload + checksum */ - fp_dbg ("goodix53x5_parse_message: cmd=0x%02x message_size=%u total_needed=%zu buf_len=%zu", - command_byte, message_size, total_needed, buf_len); + fp_dbg ("goodix53x5: recv cmd=0x%02x size=%u", command_byte, message_size); /* Reassemble from chunks if needed */ assembled = g_byte_array_new (); - fp_dbg ("goodix53x5_parse_message: appending first chunk: MIN(%zu, %d) = %zu", - buf_len, GOODIX53X5_USB_CHUNK_SIZE, - MIN (buf_len, (gsize) GOODIX53X5_USB_CHUNK_SIZE)); g_byte_array_append (assembled, buf, (guint) MIN (buf_len, (gsize) GOODIX53X5_USB_CHUNK_SIZE)); - fp_dbg ("goodix53x5_parse_message: after first append, assembled->len=%u", assembled->len); { const guint8 *remaining = buf + GOODIX53X5_USB_CHUNK_SIZE; @@ -212,12 +207,11 @@ goodix53x5_parse_message (const guint8 *buf, guint8 contd_cmd = remaining[0]; if ((contd_cmd & 0x01) == 0 || (contd_cmd & 0xFE) != command_byte) { - fp_warn ("goodix53x5: bad continuation byte 0x%02x (assembled=%u total=%zu remaining_len=%zu)", - contd_cmd, assembled->len, total_needed, remaining_len); + fp_warn ("goodix53x5: bad continuation byte 0x%02x (assembled=%u total=%zu)", + contd_cmd, assembled->len, total_needed); g_byte_array_free (assembled, TRUE); return FALSE; } - fp_dbg ("goodix53x5_parse_message: continuation chunk, assembled=%u, appending 63", assembled->len); g_byte_array_append (assembled, remaining + 1, GOODIX53X5_USB_CHUNK_SIZE - 1); remaining += GOODIX53X5_USB_CHUNK_SIZE; remaining_len -= GOODIX53X5_USB_CHUNK_SIZE; @@ -470,33 +464,6 @@ goodix53x5_gtls_derive_session_key (const guint8 *psk, memcpy (seed + 13, client_random, GOODIX53X5_RANDOM_LEN); memcpy (seed + 13 + GOODIX53X5_RANDOM_LEN, server_random, GOODIX53X5_RANDOM_LEN); - fp_dbg ("goodix53x5: derive_session_key client_random: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - client_random[0], client_random[1], client_random[2], client_random[3], - client_random[4], client_random[5], client_random[6], client_random[7], - client_random[8], client_random[9], client_random[10], client_random[11], - client_random[12], client_random[13], client_random[14], client_random[15], - client_random[16], client_random[17], client_random[18], client_random[19], - client_random[20], client_random[21], client_random[22], client_random[23], - client_random[24], client_random[25], client_random[26], client_random[27], - client_random[28], client_random[29], client_random[30], client_random[31]); - fp_dbg ("goodix53x5: derive_session_key server_random: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - server_random[0], server_random[1], server_random[2], server_random[3], - server_random[4], server_random[5], server_random[6], server_random[7], - server_random[8], server_random[9], server_random[10], server_random[11], - server_random[12], server_random[13], server_random[14], server_random[15], - server_random[16], server_random[17], server_random[18], server_random[19], - server_random[20], server_random[21], server_random[22], server_random[23], - server_random[24], server_random[25], server_random[26], server_random[27], - server_random[28], server_random[29], server_random[30], server_random[31]); - /* P_SHA256: A(0) = seed (77 bytes); A(i) = HMAC(psk, A(i-1)) which is 32 bytes */ A = g_malloc (seed_len); memcpy (A, seed, seed_len); @@ -535,58 +502,8 @@ goodix53x5_gtls_derive_session_key (const guint8 *psk, ctx->hmac_server_counter = GUINT16_FROM_LE ( *(guint16 *) &session_key[GOODIX53X5_SYMMETRIC_KEY_LEN + GOODIX53X5_SYMMETRIC_IV_LEN + GOODIX53X5_HMAC_KEY_LEN + 2]); - fp_dbg ("goodix53x5: session_key symmetric_key: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - ctx->symmetric_key[0], ctx->symmetric_key[1], - ctx->symmetric_key[2], ctx->symmetric_key[3], - ctx->symmetric_key[4], ctx->symmetric_key[5], - ctx->symmetric_key[6], ctx->symmetric_key[7], - ctx->symmetric_key[8], ctx->symmetric_key[9], - ctx->symmetric_key[10], ctx->symmetric_key[11], - ctx->symmetric_key[12], ctx->symmetric_key[13], - ctx->symmetric_key[14], ctx->symmetric_key[15]); - fp_dbg ("goodix53x5: session_key symmetric_iv: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - ctx->symmetric_iv[0], ctx->symmetric_iv[1], - ctx->symmetric_iv[2], ctx->symmetric_iv[3], - ctx->symmetric_iv[4], ctx->symmetric_iv[5], - ctx->symmetric_iv[6], ctx->symmetric_iv[7], - ctx->symmetric_iv[8], ctx->symmetric_iv[9], - ctx->symmetric_iv[10], ctx->symmetric_iv[11], - ctx->symmetric_iv[12], ctx->symmetric_iv[13], - ctx->symmetric_iv[14], ctx->symmetric_iv[15]); - fp_dbg ("goodix53x5: session_key hmac_key: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x ...", - ctx->hmac_key[0], ctx->hmac_key[1], - ctx->hmac_key[2], ctx->hmac_key[3], - ctx->hmac_key[4], ctx->hmac_key[5], - ctx->hmac_key[6], ctx->hmac_key[7], - ctx->hmac_key[8], ctx->hmac_key[9], - ctx->hmac_key[10], ctx->hmac_key[11], - ctx->hmac_key[12], ctx->hmac_key[13], - ctx->hmac_key[14], ctx->hmac_key[15]); - fp_dbg ("goodix53x5: session_key counters: client=0x%04x server=0x%04x", + fp_dbg ("goodix53x5: session_key derived: client_ctr=0x%04x server_ctr=0x%04x", ctx->hmac_client_counter, ctx->hmac_server_counter); - - /* Dump session key to file for offline debugging */ - { - FILE *f = fopen ("/tmp/goodix_session_key.bin", "wb"); - if (f) - { - fwrite (ctx->symmetric_key, 1, GOODIX53X5_SYMMETRIC_KEY_LEN, f); - fwrite (ctx->symmetric_iv, 1, GOODIX53X5_SYMMETRIC_IV_LEN, f); - fwrite (ctx->hmac_key, 1, GOODIX53X5_HMAC_KEY_LEN, f); - guint16 cc = ctx->hmac_client_counter; - guint16 sc = ctx->hmac_server_counter; - fwrite (&cc, 2, 1, f); - fwrite (&sc, 2, 1, f); - fclose (f); - fp_dbg ("goodix53x5: dumped session key to /tmp/goodix_session_key.bin"); - } - } } void @@ -730,22 +647,6 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, return FALSE; } - fp_dbg ("goodix53x5: decrypt_image: encrypted_msg_len=%zu, first 8 bytes: %02x %02x %02x %02x %02x %02x %02x %02x", - encrypted_msg_len, - encrypted_msg[0], encrypted_msg[1], encrypted_msg[2], encrypted_msg[3], - encrypted_msg[4], encrypted_msg[5], encrypted_msg[6], encrypted_msg[7]); - - /* Dump raw encrypted image to file for offline debugging */ - { - FILE *f = fopen ("/tmp/goodix_encrypted_img.bin", "wb"); - if (f) - { - fwrite (encrypted_msg, 1, encrypted_msg_len, f); - fclose (f); - fp_dbg ("goodix53x5: dumped %zu bytes to /tmp/goodix_encrypted_img.bin", encrypted_msg_len); - } - } - data_type = GUINT32_FROM_LE (*(guint32 *) encrypted_msg); if (data_type != GOODIX53X5_IMAGE_DATA_TYPE) { @@ -823,23 +724,6 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, g_byte_array_free (gea_data, TRUE); return FALSE; } - if (block_idx == 1) - { - fp_dbg ("goodix53x5: AES block1 cipher[0..15]: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - p[0], p[1], p[2], p[3], - p[4], p[5], p[6], p[7], - p[8], p[9], p[10], p[11], - p[12], p[13], p[14], p[15]); - fp_dbg ("goodix53x5: AES block1 plain[0..15]: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - aes_plain[0], aes_plain[1], aes_plain[2], aes_plain[3], - aes_plain[4], aes_plain[5], aes_plain[6], aes_plain[7], - aes_plain[8], aes_plain[9], aes_plain[10], aes_plain[11], - aes_plain[12], aes_plain[13], aes_plain[14], aes_plain[15]); - } g_byte_array_append (gea_data, aes_plain, GOODIX53X5_AES_BLOCK_SIZE); p += GOODIX53X5_AES_BLOCK_SIZE; } @@ -859,39 +743,8 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, hmac_digest = hmac_sha256 (ctx->hmac_key, GOODIX53X5_HMAC_KEY_LEN, hmac_input, 4 + tail_len); - fp_dbg ("goodix53x5: HMAC counter=0x%04x gea_len=%zu tail_off=%zu tail_len=%zu", - ctx->hmac_server_counter, gea_len, tail_off, tail_len); - fp_dbg ("goodix53x5: HMAC expected: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - payload_hmac[0], payload_hmac[1], payload_hmac[2], payload_hmac[3], - payload_hmac[4], payload_hmac[5], payload_hmac[6], payload_hmac[7], - payload_hmac[8], payload_hmac[9], payload_hmac[10], payload_hmac[11], - payload_hmac[12], payload_hmac[13], payload_hmac[14], payload_hmac[15], - payload_hmac[16], payload_hmac[17], payload_hmac[18], payload_hmac[19], - payload_hmac[20], payload_hmac[21], payload_hmac[22], payload_hmac[23], - payload_hmac[24], payload_hmac[25], payload_hmac[26], payload_hmac[27], - payload_hmac[28], payload_hmac[29], payload_hmac[30], payload_hmac[31]); - fp_dbg ("goodix53x5: HMAC computed: " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x " - "%02x %02x %02x %02x %02x %02x %02x %02x", - hmac_digest[0], hmac_digest[1], hmac_digest[2], hmac_digest[3], - hmac_digest[4], hmac_digest[5], hmac_digest[6], hmac_digest[7], - hmac_digest[8], hmac_digest[9], hmac_digest[10], hmac_digest[11], - hmac_digest[12], hmac_digest[13], hmac_digest[14], hmac_digest[15], - hmac_digest[16], hmac_digest[17], hmac_digest[18], hmac_digest[19], - hmac_digest[20], hmac_digest[21], hmac_digest[22], hmac_digest[23], - hmac_digest[24], hmac_digest[25], hmac_digest[26], hmac_digest[27], - hmac_digest[28], hmac_digest[29], hmac_digest[30], hmac_digest[31]); - fp_dbg ("goodix53x5: HMAC key: %02x %02x %02x %02x ...", - ctx->hmac_key[0], ctx->hmac_key[1], ctx->hmac_key[2], ctx->hmac_key[3]); - fp_dbg ("goodix53x5: HMAC input[0..7]: %02x %02x %02x %02x %02x %02x %02x %02x", - hmac_input[0], hmac_input[1], hmac_input[2], hmac_input[3], - hmac_input[4], hmac_input[5], hmac_input[6], hmac_input[7]); + fp_dbg ("goodix53x5: HMAC counter=0x%04x gea_len=%zu", + ctx->hmac_server_counter, gea_len); gboolean hmac_ok = (memcmp (hmac_digest, payload_hmac, 32) == 0); g_free (hmac_digest); diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.h b/libfprint/drivers/goodix53x5/goodix53x5_proto.h index aaba4c83..60f83051 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.h +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.h @@ -35,6 +35,12 @@ #define GOODIX53X5_SENSOR_HEIGHT 88 #define GOODIX53X5_SENSOR_PIXELS (GOODIX53X5_SENSOR_WIDTH * GOODIX53X5_SENSOR_HEIGHT) +/* Upscale factor applied before passing image to MINDTCT minutiae detector. + * The raw 108×88 sensor is too small for MINDTCT to find ≥10 minutiae + * (required minimum for Bozorth3 matching). A 2× bilinear upscale gives + * 216×176 pixels which is sufficient. */ +#define GOODIX53X5_ENLARGE_FACTOR 2 + /* FDT base length (12 × 2-byte values = 24 bytes) */ #define GOODIX53X5_FDT_BASE_LEN 24 diff --git a/meson.build b/meson.build index e5e229bc..c90d1e22 100644 --- a/meson.build +++ b/meson.build @@ -158,7 +158,7 @@ driver_helper_mapping = { 'aes3500' : [ 'aeslib', 'aes3k' ], 'aes4000' : [ 'aeslib', 'aes3k' ], 'uru4000' : [ 'nss' ], - 'goodix53x5' : [ 'nss' ], + 'goodix53x5' : [ 'nss', 'aes3k' ], 'elanspi' : [ 'udev' ], 'virtual_image' : [ 'virtual' ], 'virtual_device' : [ 'virtual' ], From daefcada3fda235a3fa18718d48b4b8e497a4037 Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Mon, 6 Apr 2026 15:10:24 +0200 Subject: [PATCH 3/6] data: move goodix53x5 USB IDs from unsupported to supported hwdb section Add a goodix53x5 section with 27c6:5335, 27c6:5385, and 27c6:5395 and remove those IDs from the 'Known unsupported devices' block. --- data/autosuspend.hwdb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index d476f7e1..a492525a 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -277,6 +277,13 @@ usb:v138Ap0091* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver goodix53x5 +usb:v27C6p5335* +usb:v27C6p5385* +usb:v27C6p5395* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Known unsupported devices usb:v04E8p730B* usb:v04F3p036B* @@ -352,13 +359,10 @@ usb:v27C6p521D* usb:v27C6p5301* usb:v27C6p530C* usb:v27C6p532D* -usb:v27C6p5335* usb:v27C6p533C* usb:v27C6p5381* -usb:v27C6p5385* usb:v27C6p538C* usb:v27C6p538D* -usb:v27C6p5395* usb:v27C6p5503* usb:v27C6p550A* usb:v27C6p550C* From f0029ac993c0946d29ba48941c10549a270686e3 Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Mon, 6 Apr 2026 23:44:19 +0200 Subject: [PATCH 4/6] =?UTF-8?q?goodix53x5:=20fix=20build=20=E2=80=94=20rem?= =?UTF-8?q?ove=20IDs=20from=20whitelist,=20fix=20hwdb=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three USB IDs (27c6:5335, 27c6:5385, 27c6:5395) were still in the whitelist_id_table[] of 'known unsupported' devices in fprint-list-udev-hwdb.c. During the Nix build the test tool runs with G_DEBUG=fatal-warnings, so the duplicate-ID warning becomes SIGTRAP (exit 133) and the build fails. Remove the three IDs from the whitelist. Also move the goodix53x5 section in autosuspend.hwdb to its correct alphabetical position (before goodixmoc) and reorder entries to match the id_table declaration order (5395, 5335, 5385). --- data/autosuspend.hwdb | 14 +++++++------- libfprint/fprint-list-udev-hwdb.c | 3 --- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/data/autosuspend.hwdb b/data/autosuspend.hwdb index a492525a..96b79a8b 100644 --- a/data/autosuspend.hwdb +++ b/data/autosuspend.hwdb @@ -166,6 +166,13 @@ usb:v10A5pD205* ID_AUTOSUSPEND=1 ID_PERSIST=0 +# Supported by libfprint driver goodix53x5 +usb:v27C6p5395* +usb:v27C6p5335* +usb:v27C6p5385* + ID_AUTOSUSPEND=1 + ID_PERSIST=0 + # Supported by libfprint driver goodixmoc usb:v27C6p5840* usb:v27C6p6014* @@ -277,13 +284,6 @@ usb:v138Ap0091* ID_AUTOSUSPEND=1 ID_PERSIST=0 -# Supported by libfprint driver goodix53x5 -usb:v27C6p5335* -usb:v27C6p5385* -usb:v27C6p5395* - ID_AUTOSUSPEND=1 - ID_PERSIST=0 - # Known unsupported devices usb:v04E8p730B* usb:v04F3p036B* diff --git a/libfprint/fprint-list-udev-hwdb.c b/libfprint/fprint-list-udev-hwdb.c index cf1c4ecf..80915da5 100644 --- a/libfprint/fprint-list-udev-hwdb.c +++ b/libfprint/fprint-list-udev-hwdb.c @@ -103,13 +103,10 @@ static const FpIdEntry whitelist_id_table[] = { { .vid = 0x27c6, .pid = 0x5301 }, { .vid = 0x27c6, .pid = 0x530c }, { .vid = 0x27c6, .pid = 0x532d }, - { .vid = 0x27c6, .pid = 0x5335 }, { .vid = 0x27c6, .pid = 0x533c }, { .vid = 0x27c6, .pid = 0x5381 }, - { .vid = 0x27c6, .pid = 0x5385 }, { .vid = 0x27c6, .pid = 0x538c }, { .vid = 0x27c6, .pid = 0x538d }, - { .vid = 0x27c6, .pid = 0x5395 }, { .vid = 0x27c6, .pid = 0x5503 }, { .vid = 0x27c6, .pid = 0x550a }, { .vid = 0x27c6, .pid = 0x550c }, From 545a624c1b1c013d2c965a50c7d7ebc8b3bc589a Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Tue, 7 Apr 2026 19:34:23 +0200 Subject: [PATCH 5/6] goodix53x5: clean up for upstream PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove INSTALL-GOODIX53X5.md (NixOS/personal-fork-specific content) - Remove dead fdt_event_data field from _FpiDeviceGoodix53x5 struct - Remove dead delta_img field from Goodix53x5CalibParams struct - Fix broken reference URL: nickel-org/goodix-fp-dump → goodix-fp-linux-dev/goodix-fp-dump - Remove verbose per-capture recv_buf byte dump from fp_dbg - Remove per-message fp_dbg in goodix53x5_parse_message (fires on every ACK) - Add comment in meson.build explaining why aes3k helper is used (pixman dependency) --- libfprint/drivers/goodix53x5/goodix53x5.c | 9 ++------- libfprint/drivers/goodix53x5/goodix53x5.h | 4 +--- libfprint/drivers/goodix53x5/goodix53x5_proto.c | 6 +----- libfprint/drivers/goodix53x5/goodix53x5_proto.h | 3 +-- meson.build | 4 ++++ 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/libfprint/drivers/goodix53x5/goodix53x5.c b/libfprint/drivers/goodix53x5/goodix53x5.c index ea47f6de..8ae16f72 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5.c +++ b/libfprint/drivers/goodix53x5/goodix53x5.c @@ -3,7 +3,7 @@ * Copyright (C) 2024 libfprint contributors * * Based on the reference Python implementation by goodix-fp-dump contributors - * https://github.com/nickel-org/goodix-fp-dump + * https://github.com/goodix-fp-linux-dev/goodix-fp-dump * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1516,12 +1516,7 @@ capture_ssm_state (FpiSsm *ssm, gsize raw_len = 0; guint16 *pixels = NULL; - fp_dbg ("goodix53x5: image recv_buf len=%u, first bytes: %02x %02x %02x %02x", - self->recv_buf->len, - self->recv_buf->len > 0 ? self->recv_buf->data[0] : 0, - self->recv_buf->len > 1 ? self->recv_buf->data[1] : 0, - self->recv_buf->len > 2 ? self->recv_buf->data[2] : 0, - self->recv_buf->len > 3 ? self->recv_buf->data[3] : 0); + fp_dbg ("goodix53x5: image recv_buf len=%u", self->recv_buf->len); if (!goodix53x5_check_recv (self, GOODIX53X5_CAT_IMAGE, diff --git a/libfprint/drivers/goodix53x5/goodix53x5.h b/libfprint/drivers/goodix53x5/goodix53x5.h index 3897a299..2581bee2 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5.h +++ b/libfprint/drivers/goodix53x5/goodix53x5.h @@ -3,7 +3,7 @@ * Copyright (C) 2024 libfprint contributors * * Based on the reference Python implementation by goodix-fp-dump contributors - * https://github.com/nickel-org/goodix-fp-dump + * https://github.com/goodix-fp-linux-dev/goodix-fp-dump * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -163,6 +163,4 @@ struct _FpiDeviceGoodix53x5 /* PSK hash mismatch: need to write white-box PSK */ gboolean need_psk_write; - /* First FDT event data from finger-down (used to build fdt_base_up) */ - guint8 fdt_event_data[GOODIX53X5_FDT_BASE_LEN + 4]; /* 4 header bytes */ }; diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.c b/libfprint/drivers/goodix53x5/goodix53x5_proto.c index 4db047b0..942271d7 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.c +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.c @@ -3,6 +3,7 @@ * Copyright (C) 2024 libfprint contributors * * Based on the reference Python implementation by goodix-fp-dump contributors + * https://github.com/goodix-fp-linux-dev/goodix-fp-dump * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -189,11 +190,8 @@ goodix53x5_parse_message (const guint8 *buf, command_byte = buf[0]; message_size = GUINT16_FROM_LE (*(guint16 *) (buf + 1)); - /* message_size is payload_len + 1 (for checksum); actual payload starts at buf[3] */ total_needed = 3 + (gsize) message_size; /* cmd + size_field + payload + checksum */ - fp_dbg ("goodix53x5: recv cmd=0x%02x size=%u", command_byte, message_size); - /* Reassemble from chunks if needed */ assembled = g_byte_array_new (); g_byte_array_append (assembled, buf, (guint) MIN (buf_len, (gsize) GOODIX53X5_USB_CHUNK_SIZE)); @@ -314,7 +312,6 @@ goodix53x5_parse_otp (const guint8 *otp, calib_params->delta_fdt = 0; calib_params->delta_down = 0x0D; calib_params->delta_up = 0x0B; - calib_params->delta_img = 0xC8; } else { @@ -323,7 +320,6 @@ goodix53x5_parse_otp (const guint8 *otp, calib_params->delta_fdt = tmp2 / 5; calib_params->delta_down = tmp2 / 3; calib_params->delta_up = (guint8) (calib_params->delta_down - 2); - calib_params->delta_img = 0xC8; } /* DAC values */ diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.h b/libfprint/drivers/goodix53x5/goodix53x5_proto.h index 60f83051..7d0712e5 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.h +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.h @@ -3,7 +3,7 @@ * Copyright (C) 2024 libfprint contributors * * Based on the reference Python implementation by goodix-fp-dump contributors - * https://github.com/nickel-org/goodix-fp-dump + * https://github.com/goodix-fp-linux-dev/goodix-fp-dump * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -141,7 +141,6 @@ typedef struct guint8 delta_fdt; guint8 delta_down; guint8 delta_up; - guint16 delta_img; guint8 fdt_base_down[GOODIX53X5_FDT_BASE_LEN]; guint8 fdt_base_up[GOODIX53X5_FDT_BASE_LEN]; diff --git a/meson.build b/meson.build index c90d1e22..16a6d7fc 100644 --- a/meson.build +++ b/meson.build @@ -158,6 +158,10 @@ driver_helper_mapping = { 'aes3500' : [ 'aeslib', 'aes3k' ], 'aes4000' : [ 'aeslib', 'aes3k' ], 'uru4000' : [ 'nss' ], + # goodix53x5 uses NSS for AES-128-CBC + HMAC-SHA256 (GTLS protocol). + # 'aes3k' is listed to pull in the pixman-1 dependency required by + # fpi_image_resize(), which is used to upscale the 108×88 sensor image + # before minutiae detection. 'goodix53x5' : [ 'nss', 'aes3k' ], 'elanspi' : [ 'udev' ], 'virtual_image' : [ 'virtual' ], From f3ccc41f9c5cf6b57d033a3dbc790ddd671c4445 Mon Sep 17 00:00:00 2001 From: Christoph Wedenig Date: Tue, 7 Apr 2026 19:46:20 +0200 Subject: [PATCH 6/6] goodix53x5: apply uncrustify formatting Run scripts/uncrustify.sh to bring the four driver files in line with the rest of the codebase style. --- libfprint/drivers/goodix53x5/goodix53x5.c | 54 +++--- libfprint/drivers/goodix53x5/goodix53x5.h | 34 ++-- .../drivers/goodix53x5/goodix53x5_proto.c | 110 ++++++----- .../drivers/goodix53x5/goodix53x5_proto.h | 182 +++++++++--------- 4 files changed, 193 insertions(+), 187 deletions(-) diff --git a/libfprint/drivers/goodix53x5/goodix53x5.c b/libfprint/drivers/goodix53x5/goodix53x5.c index 8ae16f72..d92883a1 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5.c +++ b/libfprint/drivers/goodix53x5/goodix53x5.c @@ -37,8 +37,8 @@ * USB endpoint addresses (bulk) * ============================================================ */ -#define GOODIX53X5_EP_OUT (0x03 | FPI_USB_ENDPOINT_OUT) -#define GOODIX53X5_EP_IN (0x81 | FPI_USB_ENDPOINT_IN) +#define GOODIX53X5_EP_OUT (0x03 | FPI_USB_ENDPOINT_OUT) +#define GOODIX53X5_EP_IN (0x81 | FPI_USB_ENDPOINT_IN) /* USB transfer timeout (ms) */ #define GOODIX53X5_TIMEOUT 5000 @@ -285,7 +285,9 @@ msg_recv_cb (FpiUsbTransfer *transfer, gsize total_needed_raw; if (total_needed_logical <= GOODIX53X5_USB_CHUNK_SIZE) - total_needed_raw = GOODIX53X5_USB_CHUNK_SIZE; + { + total_needed_raw = GOODIX53X5_USB_CHUNK_SIZE; + } else { gsize remaining = total_needed_logical - GOODIX53X5_USB_CHUNK_SIZE; @@ -352,11 +354,11 @@ goodix53x5_recv_msg (FpiDeviceGoodix53x5 *self, * @out_payload is heap-allocated; caller must g_free(). */ static gboolean -goodix53x5_parse_recv (FpiDeviceGoodix53x5 *self, - guint8 *out_category, - guint8 *out_command, - guint8 **out_payload, - gsize *out_payload_len) +goodix53x5_parse_recv (FpiDeviceGoodix53x5 *self, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len) { gboolean ok; @@ -380,11 +382,11 @@ goodix53x5_parse_recv (FpiDeviceGoodix53x5 *self, * On mismatch the SSM is failed. */ static gboolean -goodix53x5_check_recv (FpiDeviceGoodix53x5 *self, - guint8 expected_cat, - guint8 expected_cmd, - guint8 **out_payload, - gsize *out_payload_len) +goodix53x5_check_recv (FpiDeviceGoodix53x5 *self, + guint8 expected_cat, + guint8 expected_cmd, + guint8 **out_payload, + gsize *out_payload_len) { guint8 cat, cmd; @@ -450,8 +452,8 @@ goodix53x5_send_mcu (FpiDeviceGoodix53x5 *self, */ static guint8 * goodix53x5_parse_mcu_recv (FpiDeviceGoodix53x5 *self, - guint32 expected_type, - gsize *out_data_len) + guint32 expected_type, + gsize *out_data_len) { guint8 *payload = NULL; gsize payload_len = 0; @@ -531,8 +533,8 @@ goodix53x5_prod_read (FpiDeviceGoodix53x5 *self, */ static guint8 * goodix53x5_parse_prod_read_recv (FpiDeviceGoodix53x5 *self, - guint32 expected_type, - gsize *out_len) + guint32 expected_type, + gsize *out_len) { guint8 *payload = NULL; gsize payload_len = 0; @@ -865,10 +867,8 @@ init_ssm_state (FpiSsm *ssm, case INIT_WRITE_PSK_PARSE: if (self->need_psk_write) - { - if (!goodix53x5_parse_prod_write_recv (self)) - break; - } + if (!goodix53x5_parse_prod_write_recv (self)) + break; fpi_ssm_next_state (ssm); break; @@ -1687,8 +1687,8 @@ goodix53x5_img_open (FpImageDevice *dev) if (NSS_NoDB_Init (".") != SECSuccess) { fpi_image_device_open_complete (dev, - fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, - "Failed to initialise NSS")); + fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL, + "Failed to initialise NSS")); return; } @@ -1774,16 +1774,14 @@ goodix53x5_deactivate (FpImageDevice *dev) self->deactivating = TRUE; if (self->ssm == NULL) - { - /* No SSM running — complete immediately */ - fpi_image_device_deactivate_complete (dev, NULL); - } + /* No SSM running — complete immediately */ + fpi_image_device_deactivate_complete (dev, NULL); /* Otherwise the running SSM will see deactivating==TRUE and finish up */ } static void goodix53x5_change_state (FpImageDevice *dev, - FpiImageDeviceState state) + FpiImageDeviceState state) { if (state == FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_ON) goodix53x5_start_capture_ssm (dev); diff --git a/libfprint/drivers/goodix53x5/goodix53x5.h b/libfprint/drivers/goodix53x5/goodix53x5.h index 2581bee2..1105a75a 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5.h +++ b/libfprint/drivers/goodix53x5/goodix53x5.h @@ -33,17 +33,17 @@ G_DECLARE_FINAL_TYPE (FpiDeviceGoodix53x5, fpi_device_goodix53x5, * This encodes the all-zero 32-byte PSK in the device's white-box format. */ #define GOODIX53X5_PSK_WHITE_BOX \ - "\xec\x35\xae\x3a\xbb\x45\xed\x3f\x12\xc4\x75\x1f\x1e\x5c\x2c\xc0" \ - "\x5b\x3c\x54\x52\xe9\x10\x4d\x9f\x2a\x31\x18\x64\x4f\x37\xa0\x4b" \ - "\x6f\xd6\x6b\x1d\x97\xcf\x80\xf1\x34\x5f\x76\xc8\x4f\x03\xff\x30" \ - "\xbb\x51\xbf\x30\x8f\x2a\x98\x75\xc4\x1e\x65\x92\xcd\x2a\x2f\x9e" \ - "\x60\x80\x9b\x17\xb5\x31\x60\x37\xb6\x9b\xb2\xfa\x5d\x4c\x8a\xc3" \ - "\x1e\xdb\x33\x94\x04\x6e\xc0\x6b\xbd\xac\xc5\x7d\xa6\xa7\x56\xc5" + "\xec\x35\xae\x3a\xbb\x45\xed\x3f\x12\xc4\x75\x1f\x1e\x5c\x2c\xc0" \ + "\x5b\x3c\x54\x52\xe9\x10\x4d\x9f\x2a\x31\x18\x64\x4f\x37\xa0\x4b" \ + "\x6f\xd6\x6b\x1d\x97\xcf\x80\xf1\x34\x5f\x76\xc8\x4f\x03\xff\x30" \ + "\xbb\x51\xbf\x30\x8f\x2a\x98\x75\xc4\x1e\x65\x92\xcd\x2a\x2f\x9e" \ + "\x60\x80\x9b\x17\xb5\x31\x60\x37\xb6\x9b\xb2\xfa\x5d\x4c\x8a\xc3" \ + "\x1e\xdb\x33\x94\x04\x6e\xc0\x6b\xbd\xac\xc5\x7d\xa6\xa7\x56\xc5" /* SHA-256 of the all-zero 32-byte PSK */ #define GOODIX53X5_PSK_HASH \ - "\x66\x68\x7a\xad\xf8\x62\xbd\x77\x6c\x8f\xc1\x8b\x8e\x9f\x8e\x20" \ - "\x08\x97\x14\x85\x6e\xe2\x33\xb3\x90\x2a\x59\x1d\x0d\x5f\x29\x25" + "\x66\x68\x7a\xad\xf8\x62\xbd\x77\x6c\x8f\xc1\x8b\x8e\x9f\x8e\x20" \ + "\x08\x97\x14\x85\x6e\xe2\x33\xb3\x90\x2a\x59\x1d\x0d\x5f\x29\x25" /* * Init SSM states — run once on img_open: @@ -138,29 +138,29 @@ typedef enum { */ struct _FpiDeviceGoodix53x5 { - FpImageDevice parent; + FpImageDevice parent; /* USB endpoints (discovered during open) */ - guint8 ep_in; - guint8 ep_out; + guint8 ep_in; + guint8 ep_out; /* Current running SSM */ - FpiSsm *ssm; + FpiSsm *ssm; /* Receive buffer — large enough for the biggest message we expect * (encrypted image: ~0x5000+ bytes in many chunks) */ - GByteArray *recv_buf; + GByteArray *recv_buf; /* Calibration data derived from OTP */ - Goodix53x5CalibParams calib; + Goodix53x5CalibParams calib; /* GTLS context — valid after handshake */ - Goodix53x5GTLSCtx gtls; + Goodix53x5GTLSCtx gtls; /* Deactivation requested flag */ - gboolean deactivating; + gboolean deactivating; /* PSK hash mismatch: need to write white-box PSK */ - gboolean need_psk_write; + gboolean need_psk_write; }; diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.c b/libfprint/drivers/goodix53x5/goodix53x5_proto.c index 942271d7..fb2f78a7 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.c +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.c @@ -86,10 +86,10 @@ const guint8 goodix53x5_otp_hash_table[256] = { */ guint8 * goodix53x5_encode_message (guint8 category, - guint8 command, - const guint8 *payload, - gsize payload_len, - gsize *out_len) + guint8 command, + const guint8 *payload, + gsize payload_len, + gsize *out_len) { guint8 command_byte; guint16 size_field; @@ -167,12 +167,12 @@ goodix53x5_encode_message (guint8 category, * one full message (with possible trailing zeros from a single bulk read). */ gboolean -goodix53x5_parse_message (const guint8 *buf, - gsize buf_len, - guint8 *out_category, - guint8 *out_command, - guint8 **out_payload, - gsize *out_payload_len) +goodix53x5_parse_message (const guint8 *buf, + gsize buf_len, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len) { guint8 command_byte; guint16 message_size; @@ -261,7 +261,7 @@ goodix53x5_parse_message (const guint8 *buf, guint8 goodix53x5_compute_otp_hash (const guint8 *data, - gsize len) + gsize len) { guint8 checksum = 0; @@ -272,8 +272,8 @@ goodix53x5_compute_otp_hash (const guint8 *data, } gboolean -goodix53x5_parse_otp (const guint8 *otp, - gsize otp_len, +goodix53x5_parse_otp (const guint8 *otp, + gsize otp_len, Goodix53x5CalibParams *calib_params) { guint8 hash_data[64]; @@ -350,10 +350,10 @@ goodix53x5_parse_otp (const guint8 *otp, /* ---------- Sensor configuration ---------- */ static void -replace_value_in_section (guint8 *config, - guint8 section_num, - guint16 tag, - guint16 value) +replace_value_in_section (guint8 *config, + guint8 section_num, + guint16 tag, + guint16 value) { const guint8 *section_table = config + 1; guint8 section_base = section_table[section_num * 2]; @@ -392,7 +392,7 @@ fix_config_checksum (guint8 *config) void goodix53x5_build_config (const Goodix53x5CalibParams *calib_params, - guint8 *out_config) + guint8 *out_config) { memcpy (out_config, goodix53x5_default_config, 256); @@ -403,13 +403,13 @@ goodix53x5_build_config (const Goodix53x5CalibParams *calib_params, /* Patch DAC_L in sections 2, 3 */ replace_value_in_section (out_config, 2, GOODIX53X5_CFG_DAC_L_TAG, - (guint16) ((calib_params->dac_l << 4) | 8)); + (guint16) ((calib_params->dac_l << 4) | 8)); replace_value_in_section (out_config, 3, GOODIX53X5_CFG_DAC_L_TAG, - (guint16) ((calib_params->dac_l << 4) | 8)); + (guint16) ((calib_params->dac_l << 4) | 8)); /* Patch DELTA_DOWN in section 2 */ replace_value_in_section (out_config, 2, GOODIX53X5_CFG_DELTA_DOWN_TAG, - (guint16) ((calib_params->delta_down << 8) | 0x80)); + (guint16) ((calib_params->delta_down << 8) | 0x80)); fix_config_checksum (out_config); } @@ -528,13 +528,13 @@ aes128_cbc_decrypt (const guint8 *key, guint8 *plaintext) { PK11SlotInfo *slot = NULL; - PK11SymKey *symkey = NULL; - SECItem iv_item = { siBuffer, (unsigned char *) iv, 16 }; - SECItem *param = NULL; - PK11Context *ctx = NULL; - int outlen = 0; - unsigned int finallen = 0; - gboolean ok = FALSE; + PK11SymKey *symkey = NULL; + SECItem iv_item = { siBuffer, (unsigned char *) iv, 16 }; + SECItem *param = NULL; + PK11Context *ctx = NULL; + int outlen = 0; + unsigned int finallen = 0; + gboolean ok = FALSE; slot = PK11_GetBestSlot (CKM_AES_CBC, NULL); if (!slot) @@ -567,10 +567,14 @@ aes128_cbc_decrypt (const guint8 *key, ok = TRUE; out: - if (ctx) PK11_DestroyContext (ctx, PR_TRUE); - if (param) SECITEM_FreeItem (param, PR_TRUE); - if (symkey) PK11_FreeSymKey (symkey); - if (slot) PK11_FreeSlot (slot); + if (ctx) + PK11_DestroyContext (ctx, PR_TRUE); + if (param) + SECITEM_FreeItem (param, PR_TRUE); + if (symkey) + PK11_FreeSymKey (symkey); + if (slot) + PK11_FreeSlot (slot); return ok; } @@ -592,7 +596,7 @@ gea_decrypt (const guint8 *gea_key_bytes, uVar3 = (key >> 1 ^ key) & 0xFFFFFFFF; uVar2 = ((((((( - (((key >> 0xF) & 0x2000) | (key & 0x1000000)) >> 1 | (key & 0x20000)) >> 2 + (((key >> 0xF) & 0x2000) | (key & 0x1000000)) >> 1 | (key & 0x20000)) >> 2 | (key & 0x1000)) >> 3 | ((key >> 7 ^ key) & 0x80000)) >> 1 | ((key >> 0xF ^ key) & 0x4000)) >> 2 | (key & 0x2000)) >> 2 | (uVar3 & 0x40) | (key & 0x20)) >> 1 | @@ -620,10 +624,10 @@ gea_decrypt (const guint8 *gea_key_bytes, gboolean goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, - const guint8 *encrypted_msg, - gsize encrypted_msg_len, - guint8 **out_raw, - gsize *out_raw_len) + const guint8 *encrypted_msg, + gsize encrypted_msg_len, + guint8 **out_raw, + gsize *out_raw_len) { guint32 data_type; guint32 msg_length; @@ -688,7 +692,9 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, /* GEA block (appended as-is) */ gsize block_size; if (block_idx == 0) - block_size = GOODIX53X5_GEA_FIRST_BLOCK_SIZE; + { + block_size = GOODIX53X5_GEA_FIRST_BLOCK_SIZE; + } else if (block_idx == GOODIX53X5_ENCRYPT_BLOCKS - 1) { /* Last even block: take remaining payload. @@ -704,7 +710,9 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, block_size = payload_len - used; } else - block_size = GOODIX53X5_GEA_BLOCK_SIZE; + { + block_size = GOODIX53X5_GEA_BLOCK_SIZE; + } g_byte_array_append (gea_data, p, (guint) block_size); p += block_size; @@ -780,9 +788,9 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, * implementation: bytes [b0,b1,b2,b3] → value = b2<<24 | b3<<16 | b0<<8 | b1 * (each 16-bit word is byte-swapped, then the two words are big-endian). */ reported_crc = ((guint32) gea_buf[gea_len + 2] << 24) - | ((guint32) gea_buf[gea_len + 3] << 16) - | ((guint32) gea_buf[gea_len + 0] << 8) - | (guint32) gea_buf[gea_len + 1]; + | ((guint32) gea_buf[gea_len + 3] << 16) + | ((guint32) gea_buf[gea_len + 0] << 8) + | (guint32) gea_buf[gea_len + 1]; /* CRC32/MPEG-2 = CRC32 with initial value 0xFFFFFFFF, no final XOR, * bit-reversed poly 0x04C11DB7. @@ -821,9 +829,9 @@ goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, /* ---------- Image decoding (12-bit packed pixels) ---------- */ gboolean -goodix53x5_decode_image (const guint8 *raw, - gsize raw_len, - guint16 **out_pixels) +goodix53x5_decode_image (const guint8 *raw, + gsize raw_len, + guint16 **out_pixels) { gsize n_pixels; guint16 *pixels; @@ -869,7 +877,7 @@ goodix53x5_decode_image (const guint8 *raw, void goodix53x5_generate_fdt_base (const guint8 *fdt_data, - guint8 *out_fdt_base) + guint8 *out_fdt_base) { for (gsize i = 0; i < GOODIX53X5_FDT_BASE_LEN; i += 2) { @@ -882,9 +890,9 @@ goodix53x5_generate_fdt_base (const guint8 *fdt_data, void goodix53x5_generate_fdt_up_base (const guint8 *fdt_data, - guint16 touch_flag, - const Goodix53x5CalibParams *calib_params, - guint8 *out_fdt_base_up) + guint16 touch_flag, + const Goodix53x5CalibParams *calib_params, + guint8 *out_fdt_base_up) { guint16 fdt_vals[12]; guint16 fdt_base_up_vals[12]; @@ -916,8 +924,8 @@ goodix53x5_generate_fdt_up_base (const guint8 *fdt_data, gboolean goodix53x5_is_fdt_base_valid (const guint8 *fdt_data_1, - const guint8 *fdt_data_2, - guint8 max_delta) + const guint8 *fdt_data_2, + guint8 max_delta) { for (gsize i = 0; i < GOODIX53X5_FDT_BASE_LEN; i += 2) { diff --git a/libfprint/drivers/goodix53x5/goodix53x5_proto.h b/libfprint/drivers/goodix53x5/goodix53x5_proto.h index 7d0712e5..86ee75e2 100644 --- a/libfprint/drivers/goodix53x5/goodix53x5_proto.h +++ b/libfprint/drivers/goodix53x5/goodix53x5_proto.h @@ -28,103 +28,103 @@ #include /* USB transport constants */ -#define GOODIX53X5_USB_CHUNK_SIZE 0x40 /* 64 bytes per USB bulk chunk */ +#define GOODIX53X5_USB_CHUNK_SIZE 0x40 /* 64 bytes per USB bulk chunk */ /* Sensor image dimensions */ -#define GOODIX53X5_SENSOR_WIDTH 108 -#define GOODIX53X5_SENSOR_HEIGHT 88 -#define GOODIX53X5_SENSOR_PIXELS (GOODIX53X5_SENSOR_WIDTH * GOODIX53X5_SENSOR_HEIGHT) +#define GOODIX53X5_SENSOR_WIDTH 108 +#define GOODIX53X5_SENSOR_HEIGHT 88 +#define GOODIX53X5_SENSOR_PIXELS (GOODIX53X5_SENSOR_WIDTH * GOODIX53X5_SENSOR_HEIGHT) /* Upscale factor applied before passing image to MINDTCT minutiae detector. * The raw 108×88 sensor is too small for MINDTCT to find ≥10 minutiae * (required minimum for Bozorth3 matching). A 2× bilinear upscale gives * 216×176 pixels which is sufficient. */ -#define GOODIX53X5_ENLARGE_FACTOR 2 +#define GOODIX53X5_ENLARGE_FACTOR 2 /* FDT base length (12 × 2-byte values = 24 bytes) */ -#define GOODIX53X5_FDT_BASE_LEN 24 +#define GOODIX53X5_FDT_BASE_LEN 24 /* GTLS session key length */ #define GOODIX53X5_GTLS_SESSION_KEY_LEN 0x44 /* 68 bytes */ -#define GOODIX53X5_SYMMETRIC_KEY_LEN 16 -#define GOODIX53X5_SYMMETRIC_IV_LEN 16 -#define GOODIX53X5_HMAC_KEY_LEN 32 -#define GOODIX53X5_RANDOM_LEN 32 -#define GOODIX53X5_IDENTITY_LEN 32 -#define GOODIX53X5_PSK_LEN 32 -#define GOODIX53X5_PSK_WHITE_BOX_LEN 96 +#define GOODIX53X5_SYMMETRIC_KEY_LEN 16 +#define GOODIX53X5_SYMMETRIC_IV_LEN 16 +#define GOODIX53X5_HMAC_KEY_LEN 32 +#define GOODIX53X5_RANDOM_LEN 32 +#define GOODIX53X5_IDENTITY_LEN 32 +#define GOODIX53X5_PSK_LEN 32 +#define GOODIX53X5_PSK_WHITE_BOX_LEN 96 /* Encrypted image data type tag */ -#define GOODIX53X5_IMAGE_DATA_TYPE 0xAA01u +#define GOODIX53X5_IMAGE_DATA_TYPE 0xAA01u /* Number of interleaved encryption blocks in image payload */ -#define GOODIX53X5_ENCRYPT_BLOCKS 15 +#define GOODIX53X5_ENCRYPT_BLOCKS 15 /* Size of even (GEA) blocks after block 0 */ -#define GOODIX53X5_GEA_BLOCK_SIZE 0x3F0 +#define GOODIX53X5_GEA_BLOCK_SIZE 0x3F0 /* Size of first GEA block (smaller) */ #define GOODIX53X5_GEA_FIRST_BLOCK_SIZE 0x3A7 /* Size of each AES-CBC block */ -#define GOODIX53X5_AES_BLOCK_SIZE 0x3F0 +#define GOODIX53X5_AES_BLOCK_SIZE 0x3F0 /* GTLS MCU message types */ -#define GOODIX53X5_MCU_CLIENT_HELLO 0xFF01u -#define GOODIX53X5_MCU_SERVER_IDENTITY 0xFF02u -#define GOODIX53X5_MCU_CLIENT_IDENTITY 0xFF03u -#define GOODIX53X5_MCU_SERVER_DONE 0xFF04u +#define GOODIX53X5_MCU_CLIENT_HELLO 0xFF01u +#define GOODIX53X5_MCU_SERVER_IDENTITY 0xFF02u +#define GOODIX53X5_MCU_CLIENT_IDENTITY 0xFF03u +#define GOODIX53X5_MCU_SERVER_DONE 0xFF04u /* Production read/write types */ -#define GOODIX53X5_PROD_SEALED_PSK 0xB001u -#define GOODIX53X5_PROD_PSK_WHITE_BOX 0xB002u -#define GOODIX53X5_PROD_PSK_HASH 0xB003u +#define GOODIX53X5_PROD_SEALED_PSK 0xB001u +#define GOODIX53X5_PROD_PSK_WHITE_BOX 0xB002u +#define GOODIX53X5_PROD_PSK_HASH 0xB003u /* Message categories */ -#define GOODIX53X5_CAT_IMAGE 0x2 -#define GOODIX53X5_CAT_FDT 0x3 -#define GOODIX53X5_CAT_SLEEP 0x6 -#define GOODIX53X5_CAT_READ_REG 0x8 -#define GOODIX53X5_CAT_CONFIG 0x9 -#define GOODIX53X5_CAT_RESET 0xA -#define GOODIX53X5_CAT_ACK 0xB -#define GOODIX53X5_CAT_MCU 0xD -#define GOODIX53X5_CAT_PRODUCTION 0xE +#define GOODIX53X5_CAT_IMAGE 0x2 +#define GOODIX53X5_CAT_FDT 0x3 +#define GOODIX53X5_CAT_SLEEP 0x6 +#define GOODIX53X5_CAT_READ_REG 0x8 +#define GOODIX53X5_CAT_CONFIG 0x9 +#define GOODIX53X5_CAT_RESET 0xA +#define GOODIX53X5_CAT_ACK 0xB +#define GOODIX53X5_CAT_MCU 0xD +#define GOODIX53X5_CAT_PRODUCTION 0xE /* Message commands within categories */ -#define GOODIX53X5_CMD_IMAGE_GET 0x0 -#define GOODIX53X5_CMD_FDT_DOWN 0x1 -#define GOODIX53X5_CMD_FDT_UP 0x2 -#define GOODIX53X5_CMD_FDT_MANUAL 0x3 -#define GOODIX53X5_CMD_SLEEP 0x0 -#define GOODIX53X5_CMD_READ_REG 0x1 -#define GOODIX53X5_CMD_CONFIG_UPLOAD 0x0 -#define GOODIX53X5_CMD_RESET 0x1 -#define GOODIX53X5_CMD_READ_OTP 0x3 -#define GOODIX53X5_CMD_FW_VERSION 0x4 -#define GOODIX53X5_CMD_EC_CONTROL 0x7 -#define GOODIX53X5_CMD_MCU 0x1 -#define GOODIX53X5_CMD_PROD_WRITE 0x1 -#define GOODIX53X5_CMD_PROD_READ 0x2 +#define GOODIX53X5_CMD_IMAGE_GET 0x0 +#define GOODIX53X5_CMD_FDT_DOWN 0x1 +#define GOODIX53X5_CMD_FDT_UP 0x2 +#define GOODIX53X5_CMD_FDT_MANUAL 0x3 +#define GOODIX53X5_CMD_SLEEP 0x0 +#define GOODIX53X5_CMD_READ_REG 0x1 +#define GOODIX53X5_CMD_CONFIG_UPLOAD 0x0 +#define GOODIX53X5_CMD_RESET 0x1 +#define GOODIX53X5_CMD_READ_OTP 0x3 +#define GOODIX53X5_CMD_FW_VERSION 0x4 +#define GOODIX53X5_CMD_EC_CONTROL 0x7 +#define GOODIX53X5_CMD_MCU 0x1 +#define GOODIX53X5_CMD_PROD_WRITE 0x1 +#define GOODIX53X5_CMD_PROD_READ 0x2 /* FDT operation opcode bytes */ -#define GOODIX53X5_FDT_DOWN_OPCODE 0x0C -#define GOODIX53X5_FDT_UP_OPCODE 0x0E +#define GOODIX53X5_FDT_DOWN_OPCODE 0x0C +#define GOODIX53X5_FDT_UP_OPCODE 0x0E /* Manual FDT opcodes: 0x0D (TX on), 0x8D (TX off) */ -#define GOODIX53X5_FDT_MANUAL_TX_ON 0x0D -#define GOODIX53X5_FDT_MANUAL_TX_OFF 0x8D +#define GOODIX53X5_FDT_MANUAL_TX_ON 0x0D +#define GOODIX53X5_FDT_MANUAL_TX_OFF 0x8D /* Image capture opcode flags */ -#define GOODIX53X5_IMG_TX_ENABLE 0x01 -#define GOODIX53X5_IMG_TX_DISABLE 0x81 -#define GOODIX53X5_IMG_FINGER_FLAG 0x40 -#define GOODIX53X5_IMG_HV_VALUE 0x06 -#define GOODIX53X5_IMG_HV_DISABLED 0x10 +#define GOODIX53X5_IMG_TX_ENABLE 0x01 +#define GOODIX53X5_IMG_TX_DISABLE 0x81 +#define GOODIX53X5_IMG_FINGER_FLAG 0x40 +#define GOODIX53X5_IMG_HV_VALUE 0x06 +#define GOODIX53X5_IMG_HV_DISABLED 0x10 /* Default sensor config (256 bytes) - from reference implementation */ extern const guint8 goodix53x5_default_config[256]; /* Config register tags */ -#define GOODIX53X5_CFG_TCODE_TAG 0x005Cu -#define GOODIX53X5_CFG_DAC_L_TAG 0x0220u -#define GOODIX53X5_CFG_DELTA_DOWN_TAG 0x0082u +#define GOODIX53X5_CFG_TCODE_TAG 0x005Cu +#define GOODIX53X5_CFG_DAC_L_TAG 0x0220u +#define GOODIX53X5_CFG_DELTA_DOWN_TAG 0x0082u /* OTP custom CRC-8 table (256 bytes) */ extern const guint8 goodix53x5_otp_hash_table[256]; @@ -138,13 +138,13 @@ typedef struct guint16 dac_h; guint16 dac_l; - guint8 delta_fdt; - guint8 delta_down; - guint8 delta_up; + guint8 delta_fdt; + guint8 delta_down; + guint8 delta_up; - guint8 fdt_base_down[GOODIX53X5_FDT_BASE_LEN]; - guint8 fdt_base_up[GOODIX53X5_FDT_BASE_LEN]; - guint8 fdt_base_manual[GOODIX53X5_FDT_BASE_LEN]; + guint8 fdt_base_down[GOODIX53X5_FDT_BASE_LEN]; + guint8 fdt_base_up[GOODIX53X5_FDT_BASE_LEN]; + guint8 fdt_base_manual[GOODIX53X5_FDT_BASE_LEN]; } Goodix53x5CalibParams; /* @@ -152,14 +152,14 @@ typedef struct */ typedef struct { - guint8 psk[GOODIX53X5_PSK_LEN]; - guint8 client_random[GOODIX53X5_RANDOM_LEN]; - guint8 server_random[GOODIX53X5_RANDOM_LEN]; - guint8 symmetric_key[GOODIX53X5_SYMMETRIC_KEY_LEN]; - guint8 symmetric_iv[GOODIX53X5_SYMMETRIC_IV_LEN]; - guint8 hmac_key[GOODIX53X5_HMAC_KEY_LEN]; - guint16 hmac_client_counter; - guint16 hmac_server_counter; + guint8 psk[GOODIX53X5_PSK_LEN]; + guint8 client_random[GOODIX53X5_RANDOM_LEN]; + guint8 server_random[GOODIX53X5_RANDOM_LEN]; + guint8 symmetric_key[GOODIX53X5_SYMMETRIC_KEY_LEN]; + guint8 symmetric_iv[GOODIX53X5_SYMMETRIC_IV_LEN]; + guint8 hmac_key[GOODIX53X5_HMAC_KEY_LEN]; + guint16 hmac_client_counter; + guint16 hmac_server_counter; gboolean connected; } Goodix53x5GTLSCtx; @@ -171,11 +171,11 @@ typedef struct * Returns heap-allocated buffer + sets @out_len. * Caller must g_free(). */ -guint8 * goodix53x5_encode_message (guint8 category, - guint8 command, - const guint8 *payload, - gsize payload_len, - gsize *out_len); +guint8 * goodix53x5_encode_message (guint8 category, + guint8 command, + const guint8 *payload, + gsize payload_len, + gsize *out_len); /** * goodix53x5_parse_message: @@ -183,12 +183,12 @@ guint8 * goodix53x5_encode_message (guint8 category, * Returns TRUE on success. Decoded category/command/payload returned in out params. * @out_payload is heap-allocated, caller must g_free(). */ -gboolean goodix53x5_parse_message (const guint8 *buf, - gsize buf_len, - guint8 *out_category, - guint8 *out_command, - guint8 **out_payload, - gsize *out_payload_len); +gboolean goodix53x5_parse_message (const guint8 *buf, + gsize buf_len, + guint8 *out_category, + guint8 *out_command, + guint8 **out_payload, + gsize *out_payload_len); /** * goodix53x5_compute_otp_hash: @@ -202,9 +202,9 @@ guint8 goodix53x5_compute_otp_hash (const guint8 *data, * Parse OTP data and fill @calib_params. * Returns TRUE on success. */ -gboolean goodix53x5_parse_otp (const guint8 *otp, - gsize otp_len, - Goodix53x5CalibParams *calib_params); +gboolean goodix53x5_parse_otp (const guint8 *otp, + gsize otp_len, + Goodix53x5CalibParams *calib_params); /** * goodix53x5_build_config: @@ -212,7 +212,7 @@ gboolean goodix53x5_parse_otp (const guint8 *otp, * @out_config must be 256 bytes. */ void goodix53x5_build_config (const Goodix53x5CalibParams *calib_params, - guint8 *out_config); + guint8 *out_config); /** * goodix53x5_gtls_derive_session_key: @@ -261,9 +261,9 @@ gboolean goodix53x5_gtls_decrypt_image (Goodix53x5GTLSCtx *ctx, * @out_pixels: heap-allocated array of GOODIX53X5_SENSOR_PIXELS guint16, caller must g_free() * Returns TRUE on success. */ -gboolean goodix53x5_decode_image (const guint8 *raw, - gsize raw_len, - guint16 **out_pixels); +gboolean goodix53x5_decode_image (const guint8 *raw, + gsize raw_len, + guint16 **out_pixels); /** * goodix53x5_update_fdt_base: