mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-14 09:58:07 +02:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
parent
424321ad2c
commit
d3d73d717f
11 changed files with 5085 additions and 5341 deletions
|
|
@ -322,136 +322,20 @@ foreach test_name: unit_tests
|
|||
)
|
||||
endforeach
|
||||
|
||||
# Validity TLS unit tests (needs driver library + OpenSSL)
|
||||
# Validity unit tests (needs driver library + OpenSSL)
|
||||
if 'validity' in supported_drivers
|
||||
openssl_dep = dependency('openssl', version: '>= 3.0', required: false)
|
||||
if openssl_dep.found()
|
||||
validity_tls_test = executable('test-validity-tls',
|
||||
sources: 'test-validity-tls.c',
|
||||
validity_test = executable('test-validity',
|
||||
sources: 'test-validity.c',
|
||||
dependencies: [ libfprint_private_dep, openssl_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
link_whole: test_utils,
|
||||
install: false,
|
||||
)
|
||||
test('validity-tls',
|
||||
validity_tls_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
||||
# Validity fwext unit tests (no OpenSSL needed)
|
||||
validity_fwext_test = executable('test-validity-fwext',
|
||||
sources: 'test-validity-fwext.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-fwext',
|
||||
validity_fwext_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity sensor identification unit tests (no OpenSSL needed)
|
||||
validity_sensor_test = executable('test-validity-sensor',
|
||||
sources: 'test-validity-sensor.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-sensor',
|
||||
validity_sensor_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity capture infrastructure unit tests (needs OpenSSL for clean slate)
|
||||
if openssl_dep.found()
|
||||
validity_capture_test = executable('test-validity-capture',
|
||||
sources: 'test-validity-capture.c',
|
||||
dependencies: [ libfprint_private_dep, openssl_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-capture',
|
||||
validity_capture_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
|
||||
# Validity DB operations unit tests
|
||||
validity_db_test = executable('test-validity-db',
|
||||
sources: 'test-validity-db.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-db',
|
||||
validity_db_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity verify/identify/delete/clear regression tests
|
||||
validity_verify_test = executable('test-validity-verify',
|
||||
sources: 'test-validity-verify.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
link_whole: test_utils,
|
||||
install: false,
|
||||
)
|
||||
test('validity-verify',
|
||||
validity_verify_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity enrollment response parsing unit tests
|
||||
validity_enroll_test = executable('test-validity-enroll',
|
||||
sources: 'test-validity-enroll.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-enroll',
|
||||
validity_enroll_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity HAL unit tests
|
||||
validity_hal_test = executable('test-validity-hal',
|
||||
sources: 'test-validity-hal.c',
|
||||
dependencies: [ libfprint_private_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-hal',
|
||||
validity_hal_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
||||
# Validity pairing unit tests
|
||||
if openssl_dep.found()
|
||||
validity_pair_test = executable('test-validity-pair',
|
||||
sources: 'test-validity-pair.c',
|
||||
dependencies: [ libfprint_private_dep, openssl_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-pair',
|
||||
validity_pair_test,
|
||||
test('validity',
|
||||
validity_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,764 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity database operations
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-byte-utils.h"
|
||||
|
||||
#include "drivers/validity/validity_db.h"
|
||||
#include "drivers/validity/validity.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* T6.1: test_cmd_db_info
|
||||
*
|
||||
* Verify cmd 0x45 (DB info) is a single-byte command.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_db_info (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_info (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 1);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_DB_INFO);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.2: test_cmd_get_user_storage
|
||||
*
|
||||
* Verify cmd 0x4B format with a known storage name.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_user_storage (void)
|
||||
{
|
||||
gsize len;
|
||||
const gchar *name = "StgWindsor";
|
||||
gsize name_len = strlen (name) + 1; /* includes NUL */
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_user_storage (name, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 1 + 2 + 2 + name_len); /* cmd + dbid + name_len + name */
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_USER_STORAGE);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0); /* dbid = 0 → lookup by name */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, name_len);
|
||||
g_assert_cmpmem (&cmd[5], name_len, name, name_len);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.3: test_cmd_get_user_storage_null_name
|
||||
*
|
||||
* When name is NULL, should produce a command with zero name_len.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_user_storage_null_name (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_user_storage (NULL, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 5); /* cmd(1) + dbid(2) + name_len(2) */
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_USER_STORAGE);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0); /* name_len = 0 */
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.4: test_cmd_get_user
|
||||
*
|
||||
* Verify cmd 0x4A format for get-by-dbid.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_user (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_user (0x1234, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 7);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_USER);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0x1234);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[5]), ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.5: test_cmd_lookup_user
|
||||
*
|
||||
* Verify cmd 0x4A format for lookup-by-identity.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_lookup_user (void)
|
||||
{
|
||||
gsize len;
|
||||
guint8 identity[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_lookup_user (
|
||||
0x0003, identity, sizeof (identity), &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 7 + sizeof (identity));
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_USER);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0); /* dbid = 0 */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0x0003); /* storage */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[5]), ==, sizeof (identity));
|
||||
g_assert_cmpmem (&cmd[7], sizeof (identity), identity, sizeof (identity));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.6: test_cmd_new_record
|
||||
*
|
||||
* Verify cmd 0x47 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_new_record (void)
|
||||
{
|
||||
gsize len;
|
||||
guint8 data[] = { 0xAA, 0xBB };
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_new_record (
|
||||
0x0003, 0x0005, 0x0003, data, sizeof (data), &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 9 + sizeof (data));
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_NEW_RECORD);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0x0003); /* parent */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0x0005); /* type */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[5]), ==, 0x0003); /* storage */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[7]), ==, sizeof (data));
|
||||
g_assert_cmpmem (&cmd[9], sizeof (data), data, sizeof (data));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.7: test_cmd_del_record
|
||||
*
|
||||
* Verify cmd 0x48 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_del_record (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_del_record (0xABCD, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_DEL_RECORD);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0xABCD);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.8: test_cmd_create_enrollment
|
||||
*
|
||||
* Verify cmd 0x69 start and end variants.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_create_enrollment (void)
|
||||
{
|
||||
gsize len;
|
||||
|
||||
/* Start enrollment */
|
||||
g_autofree guint8 *cmd_start = validity_db_build_cmd_create_enrollment (TRUE, &len);
|
||||
|
||||
g_assert_nonnull (cmd_start);
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (cmd_start[0], ==, VCSFW_CMD_CREATE_ENROLLMENT);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&cmd_start[1]), ==, 1);
|
||||
|
||||
/* End enrollment */
|
||||
g_autofree guint8 *cmd_end = validity_db_build_cmd_create_enrollment (FALSE, &len);
|
||||
g_assert_nonnull (cmd_end);
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (cmd_end[0], ==, VCSFW_CMD_CREATE_ENROLLMENT);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&cmd_end[1]), ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.9: test_cmd_enrollment_update_start
|
||||
*
|
||||
* Verify cmd 0x68 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_enrollment_update_start (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_enrollment_update_start (0x12345678, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 9);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_ENROLLMENT_UPDATE_START);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&cmd[1]), ==, 0x12345678);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&cmd[5]), ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.10: test_cmd_match_finger
|
||||
*
|
||||
* Verify cmd 0x5E format (13 bytes per python-validity).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_match_finger (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_match_finger (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 13);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_MATCH_FINGER);
|
||||
g_assert_cmpuint (cmd[1], ==, 0x02);
|
||||
g_assert_cmpuint (cmd[2], ==, 0xFF);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[5]), ==, 0);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[7]), ==, 1);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[9]), ==, 0);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[11]), ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.11: test_cmd_get_match_result
|
||||
*
|
||||
* Verify cmd 0x60 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_match_result (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_match_result (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_MATCH_RESULT);
|
||||
/* remaining bytes should be 0 */
|
||||
g_assert_cmpuint (cmd[1], ==, 0);
|
||||
g_assert_cmpuint (cmd[2], ==, 0);
|
||||
g_assert_cmpuint (cmd[3], ==, 0);
|
||||
g_assert_cmpuint (cmd[4], ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.12: test_cmd_match_cleanup
|
||||
*
|
||||
* Verify cmd 0x62 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_match_cleanup (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_match_cleanup (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_MATCH_CLEANUP);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.13: test_cmd_get_prg_status
|
||||
*
|
||||
* Verify cmd 0x51 both normal and extended variant.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_prg_status (void)
|
||||
{
|
||||
gsize len;
|
||||
|
||||
g_autofree guint8 *normal = validity_db_build_cmd_get_prg_status (FALSE, &len);
|
||||
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (normal[0], ==, VCSFW_CMD_GET_PRG_STATUS);
|
||||
/* Normal: 00000000 */
|
||||
g_assert_cmpuint (normal[1], ==, 0);
|
||||
g_assert_cmpuint (normal[2], ==, 0);
|
||||
g_assert_cmpuint (normal[3], ==, 0);
|
||||
g_assert_cmpuint (normal[4], ==, 0);
|
||||
|
||||
g_autofree guint8 *ext = validity_db_build_cmd_get_prg_status (TRUE, &len);
|
||||
g_assert_cmpuint (len, ==, 5);
|
||||
g_assert_cmpuint (ext[0], ==, VCSFW_CMD_GET_PRG_STATUS);
|
||||
/* Extended: 00200000 LE */
|
||||
g_assert_cmpuint (ext[1], ==, 0x00);
|
||||
g_assert_cmpuint (ext[2], ==, 0x20);
|
||||
g_assert_cmpuint (ext[3], ==, 0x00);
|
||||
g_assert_cmpuint (ext[4], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.14: test_cmd_capture_stop
|
||||
*
|
||||
* Verify cmd 0x04 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_capture_stop (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_capture_stop (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 1);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_CAPTURE_STOP);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.15: test_cmd_call_cleanups
|
||||
*
|
||||
* Verify cmd 0x1a format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_call_cleanups (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_call_cleanups (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 1);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x1a);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.16: test_parse_db_info
|
||||
*
|
||||
* Construct a known db_info binary response and verify parsing.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_db_info (void)
|
||||
{
|
||||
guint8 data[0x1C]; /* 24 bytes header + 4 bytes for 2 roots */
|
||||
ValidityDbInfo info;
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
/* Header: unknown1=1, unknown0=0, total=65536, used=1024, free=64512, records=10, n_roots=2 */
|
||||
FP_WRITE_UINT32_LE (&data[0], 1); /* unknown1 */
|
||||
FP_WRITE_UINT32_LE (&data[4], 0); /* unknown0 */
|
||||
FP_WRITE_UINT32_LE (&data[8], 65536); /* total */
|
||||
FP_WRITE_UINT32_LE (&data[12], 1024); /* used */
|
||||
FP_WRITE_UINT32_LE (&data[16], 64512); /* free */
|
||||
FP_WRITE_UINT16_LE (&data[20], 10); /* records */
|
||||
FP_WRITE_UINT16_LE (&data[22], 2); /* n_roots */
|
||||
FP_WRITE_UINT16_LE (&data[24], 0x0001); /* root[0] */
|
||||
FP_WRITE_UINT16_LE (&data[26], 0x0003); /* root[1] */
|
||||
|
||||
g_assert_true (validity_db_parse_info (data, sizeof (data), &info));
|
||||
|
||||
g_assert_cmpuint (info.unknown1, ==, 1);
|
||||
g_assert_cmpuint (info.unknown0, ==, 0);
|
||||
g_assert_cmpuint (info.total, ==, 65536);
|
||||
g_assert_cmpuint (info.used, ==, 1024);
|
||||
g_assert_cmpuint (info.free_space, ==, 64512);
|
||||
g_assert_cmpuint (info.records, ==, 10);
|
||||
g_assert_cmpuint (info.n_roots, ==, 2);
|
||||
g_assert_nonnull (info.roots);
|
||||
g_assert_cmpuint (info.roots[0], ==, 1);
|
||||
g_assert_cmpuint (info.roots[1], ==, 3);
|
||||
|
||||
validity_db_info_clear (&info);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.17: test_parse_db_info_too_short
|
||||
*
|
||||
* A response shorter than 24 bytes should fail.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_db_info_too_short (void)
|
||||
{
|
||||
guint8 data[20] = { 0 };
|
||||
ValidityDbInfo info;
|
||||
|
||||
g_assert_false (validity_db_parse_info (data, sizeof (data), &info));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.18: test_parse_user_storage
|
||||
*
|
||||
* Construct a user storage response with 2 users and verify.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_user_storage (void)
|
||||
{
|
||||
/* Header: dbid=3, user_count=2, name_sz=11, unknown=0
|
||||
* User table: {dbid=10, val_sz=100}, {dbid=11, val_sz=200}
|
||||
* Name: "StgWindsor\0" */
|
||||
gsize name_len = strlen ("StgWindsor") + 1;
|
||||
gsize total = 8 + 2 * 4 + name_len;
|
||||
g_autofree guint8 *data = g_new0 (guint8, total);
|
||||
|
||||
FP_WRITE_UINT16_LE (&data[0], 3); /* dbid */
|
||||
FP_WRITE_UINT16_LE (&data[2], 2); /* user_count */
|
||||
FP_WRITE_UINT16_LE (&data[4], name_len); /* name_sz */
|
||||
FP_WRITE_UINT16_LE (&data[6], 0); /* unknown */
|
||||
|
||||
FP_WRITE_UINT16_LE (&data[8], 10); /* user[0].dbid */
|
||||
FP_WRITE_UINT16_LE (&data[10], 100); /* user[0].val_sz */
|
||||
FP_WRITE_UINT16_LE (&data[12], 11); /* user[1].dbid */
|
||||
FP_WRITE_UINT16_LE (&data[14], 200); /* user[1].val_sz */
|
||||
|
||||
memcpy (&data[16], "StgWindsor", name_len);
|
||||
|
||||
ValidityUserStorage storage;
|
||||
g_assert_true (validity_db_parse_user_storage (data, total, &storage));
|
||||
|
||||
g_assert_cmpuint (storage.dbid, ==, 3);
|
||||
g_assert_cmpuint (storage.user_count, ==, 2);
|
||||
g_assert_cmpstr (storage.name, ==, "StgWindsor");
|
||||
g_assert_nonnull (storage.user_dbids);
|
||||
g_assert_cmpuint (storage.user_dbids[0], ==, 10);
|
||||
g_assert_cmpuint (storage.user_dbids[1], ==, 11);
|
||||
g_assert_cmpuint (storage.user_val_sizes[0], ==, 100);
|
||||
g_assert_cmpuint (storage.user_val_sizes[1], ==, 200);
|
||||
|
||||
validity_user_storage_clear (&storage);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.19: test_parse_user
|
||||
*
|
||||
* Construct a user response with one finger and verify.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_user (void)
|
||||
{
|
||||
guint8 identity_bytes[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
/* Header: dbid=10, finger_count=1, unknown=0, identity_sz=4
|
||||
* Finger: dbid=20, subtype=2, storage=3, value_size=500
|
||||
* Identity: 4 bytes */
|
||||
gsize total = 8 + 8 + sizeof (identity_bytes);
|
||||
g_autofree guint8 *data = g_new0 (guint8, total);
|
||||
|
||||
FP_WRITE_UINT16_LE (&data[0], 10); /* dbid */
|
||||
FP_WRITE_UINT16_LE (&data[2], 1); /* finger_count */
|
||||
FP_WRITE_UINT16_LE (&data[4], 0); /* unknown */
|
||||
FP_WRITE_UINT16_LE (&data[6], sizeof (identity_bytes)); /* identity_sz */
|
||||
|
||||
/* Finger entry */
|
||||
FP_WRITE_UINT16_LE (&data[8], 20); /* finger.dbid */
|
||||
FP_WRITE_UINT16_LE (&data[10], 2); /* finger.subtype = right index */
|
||||
FP_WRITE_UINT16_LE (&data[12], 3); /* finger.storage */
|
||||
FP_WRITE_UINT16_LE (&data[14], 500); /* finger.value_size */
|
||||
|
||||
memcpy (&data[16], identity_bytes, sizeof (identity_bytes));
|
||||
|
||||
ValidityUser user;
|
||||
g_assert_true (validity_db_parse_user (data, total, &user));
|
||||
|
||||
g_assert_cmpuint (user.dbid, ==, 10);
|
||||
g_assert_cmpuint (user.finger_count, ==, 1);
|
||||
g_assert_nonnull (user.fingers);
|
||||
g_assert_cmpuint (user.fingers[0].dbid, ==, 20);
|
||||
g_assert_cmpuint (user.fingers[0].subtype, ==, 2);
|
||||
g_assert_cmpuint (user.fingers[0].storage, ==, 3);
|
||||
g_assert_cmpuint (user.fingers[0].value_size, ==, 500);
|
||||
g_assert_nonnull (user.identity);
|
||||
g_assert_cmpuint (user.identity_len, ==, sizeof (identity_bytes));
|
||||
g_assert_cmpmem (user.identity, user.identity_len,
|
||||
identity_bytes, sizeof (identity_bytes));
|
||||
|
||||
validity_user_clear (&user);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.20: test_parse_new_record_id
|
||||
*
|
||||
* Verify parsing of new_record response (cmd 0x47).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_new_record_id (void)
|
||||
{
|
||||
guint16 record_id;
|
||||
guint8 data[] = { 0x42, 0x00 };
|
||||
|
||||
g_assert_true (validity_db_parse_new_record_id (data, sizeof (data), &record_id));
|
||||
g_assert_cmpuint (record_id, ==, 0x0042);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.21: test_parse_new_record_id_too_short
|
||||
*
|
||||
* A 1-byte response should fail.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_new_record_id_too_short (void)
|
||||
{
|
||||
guint16 record_id;
|
||||
guint8 data[] = { 0x42 };
|
||||
|
||||
g_assert_false (validity_db_parse_new_record_id (data, sizeof (data), &record_id));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.22: test_build_identity
|
||||
*
|
||||
* Build a UUID identity and verify structure.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_identity (void)
|
||||
{
|
||||
gsize len;
|
||||
const gchar *uuid = "550e8400-e29b-41d4-a716-446655440000";
|
||||
g_autofree guint8 *id = validity_db_build_identity (uuid, &len);
|
||||
|
||||
g_assert_nonnull (id);
|
||||
/* Minimum size enforced */
|
||||
g_assert_cmpuint (len, >=, VALIDITY_IDENTITY_MIN_SIZE);
|
||||
|
||||
/* type = SID (3) */
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&id[0]), ==, VALIDITY_IDENTITY_TYPE_SID);
|
||||
|
||||
/* len field = UUID string length */
|
||||
gsize uuid_len = strlen (uuid);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&id[4]), ==, uuid_len);
|
||||
|
||||
/* UUID payload */
|
||||
g_assert_cmpmem (&id[8], uuid_len, uuid, uuid_len);
|
||||
|
||||
/* Remaining bytes should be zero-padded */
|
||||
for (gsize i = 8 + uuid_len; i < len; i++)
|
||||
g_assert_cmpuint (id[i], ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.23: test_build_finger_data
|
||||
*
|
||||
* Build finger data and verify the tagged format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_finger_data (void)
|
||||
{
|
||||
gsize len;
|
||||
guint8 template[] = { 0x11, 0x22, 0x33 };
|
||||
guint8 tid[] = { 0xAA, 0xBB };
|
||||
g_autofree guint8 *fd = validity_db_build_finger_data (
|
||||
2, template, sizeof (template), tid, sizeof (tid), &len);
|
||||
|
||||
g_assert_nonnull (fd);
|
||||
|
||||
/* Check header: subtype(2) | flags=3(2) | tinfo_len(2) | 0x20(2) */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[0]), ==, 2); /* subtype */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[2]), ==, 3); /* flags */
|
||||
|
||||
gsize expected_tinfo_len = 4 + sizeof (template) + 4 + sizeof (tid);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[4]), ==, expected_tinfo_len);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[6]), ==, 0x20);
|
||||
|
||||
/* Tag 1 (template) at offset 8 */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[8]), ==, 1);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[10]), ==, sizeof (template));
|
||||
g_assert_cmpmem (&fd[12], sizeof (template), template, sizeof (template));
|
||||
|
||||
/* Tag 2 (tid) at offset 12+3 = 15 */
|
||||
gsize tid_offset = 12 + sizeof (template);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[tid_offset]), ==, 2);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&fd[tid_offset + 2]), ==, sizeof (tid));
|
||||
g_assert_cmpmem (&fd[tid_offset + 4], sizeof (tid), tid, sizeof (tid));
|
||||
|
||||
/* Total should be header(8) + tinfo + 0x20 padding */
|
||||
gsize expected_total = 8 + expected_tinfo_len + 0x20;
|
||||
g_assert_cmpuint (len, ==, expected_total);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.24: test_db_write_enable_blob
|
||||
*
|
||||
* Verify db_write_enable blob accessor returns a valid blob.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_db_write_enable_blob (void)
|
||||
{
|
||||
gsize len;
|
||||
|
||||
/* Test with 009a device type (known to have a 3621-byte blob) */
|
||||
const guint8 *blob = validity_db_get_write_enable_blob (VALIDITY_DEV_9A, &len);
|
||||
|
||||
g_assert_nonnull (blob);
|
||||
g_assert_cmpuint (len, >, 0);
|
||||
g_assert_cmpuint (len, ==, 3621);
|
||||
|
||||
/* Test all supported device types return valid blobs */
|
||||
const guint dev_types[] = { VALIDITY_DEV_90, VALIDITY_DEV_97,
|
||||
VALIDITY_DEV_9A, VALIDITY_DEV_9D };
|
||||
for (guint i = 0; i < G_N_ELEMENTS (dev_types); i++)
|
||||
{
|
||||
gsize dbe_len;
|
||||
const guint8 *dbe = validity_db_get_write_enable_blob (dev_types[i], &dbe_len);
|
||||
g_assert_nonnull (dbe);
|
||||
g_assert_cmpuint (dbe_len, >, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.25: test_parse_record_value
|
||||
*
|
||||
* Construct a record value response and verify parsing.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_record_value (void)
|
||||
{
|
||||
guint8 value[] = { 0x01, 0x02, 0x03 };
|
||||
/* Format: dbid(2) type(2) storage(2) sz(2) pad(2) value */
|
||||
gsize total = 10 + sizeof (value);
|
||||
g_autofree guint8 *data = g_new0 (guint8, total);
|
||||
|
||||
FP_WRITE_UINT16_LE (&data[0], 42); /* dbid */
|
||||
FP_WRITE_UINT16_LE (&data[2], 8); /* type = DATA */
|
||||
FP_WRITE_UINT16_LE (&data[4], 3); /* storage */
|
||||
FP_WRITE_UINT16_LE (&data[6], sizeof (value)); /* sz */
|
||||
FP_WRITE_UINT16_LE (&data[8], 0); /* pad */
|
||||
memcpy (&data[10], value, sizeof (value));
|
||||
|
||||
ValidityDbRecord record;
|
||||
g_assert_true (validity_db_parse_record_value (data, total, &record));
|
||||
|
||||
g_assert_cmpuint (record.dbid, ==, 42);
|
||||
g_assert_cmpuint (record.type, ==, 8);
|
||||
g_assert_cmpuint (record.storage, ==, 3);
|
||||
g_assert_nonnull (record.value);
|
||||
g_assert_cmpuint (record.value_len, ==, sizeof (value));
|
||||
g_assert_cmpmem (record.value, record.value_len, value, sizeof (value));
|
||||
|
||||
validity_db_record_clear (&record);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.26: test_parse_record_children
|
||||
*
|
||||
* Construct a record children response and verify parsing.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_record_children (void)
|
||||
{
|
||||
/* Format: dbid(2) type(2) storage(2) sz(2) cnt(2) pad(2)
|
||||
* children[cnt * 4: dbid(2) type(2)] */
|
||||
gsize total = 12 + 2 * 4;
|
||||
g_autofree guint8 *data = g_new0 (guint8, total);
|
||||
|
||||
FP_WRITE_UINT16_LE (&data[0], 3); /* dbid */
|
||||
FP_WRITE_UINT16_LE (&data[2], 4); /* type = STORAGE */
|
||||
FP_WRITE_UINT16_LE (&data[4], 3); /* storage */
|
||||
FP_WRITE_UINT16_LE (&data[6], 0); /* sz */
|
||||
FP_WRITE_UINT16_LE (&data[8], 2); /* child_count */
|
||||
FP_WRITE_UINT16_LE (&data[10], 0); /* pad */
|
||||
|
||||
/* Children */
|
||||
FP_WRITE_UINT16_LE (&data[12], 10); /* child[0].dbid */
|
||||
FP_WRITE_UINT16_LE (&data[14], 5); /* child[0].type = USER */
|
||||
FP_WRITE_UINT16_LE (&data[16], 11); /* child[1].dbid */
|
||||
FP_WRITE_UINT16_LE (&data[18], 5); /* child[1].type = USER */
|
||||
|
||||
ValidityRecordChildren children;
|
||||
g_assert_true (validity_db_parse_record_children (data, total, &children));
|
||||
|
||||
g_assert_cmpuint (children.dbid, ==, 3);
|
||||
g_assert_cmpuint (children.type, ==, 4);
|
||||
g_assert_cmpuint (children.storage, ==, 3);
|
||||
g_assert_cmpuint (children.child_count, ==, 2);
|
||||
g_assert_nonnull (children.children);
|
||||
g_assert_cmpuint (children.children[0].dbid, ==, 10);
|
||||
g_assert_cmpuint (children.children[0].type, ==, 5);
|
||||
g_assert_cmpuint (children.children[1].dbid, ==, 11);
|
||||
g_assert_cmpuint (children.children[1].type, ==, 5);
|
||||
|
||||
validity_record_children_clear (&children);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.27: test_cmd_enrollment_update
|
||||
*
|
||||
* Verify cmd 0x6B format with template data.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_enrollment_update (void)
|
||||
{
|
||||
gsize len;
|
||||
guint8 prev[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_enrollment_update (
|
||||
prev, sizeof (prev), &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 1 + sizeof (prev));
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_ENROLLMENT_UPDATE);
|
||||
g_assert_cmpmem (&cmd[1], sizeof (prev), prev, sizeof (prev));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.28: test_cmd_get_record_value
|
||||
*
|
||||
* Verify cmd 0x49 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_record_value (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_record_value (0x5678, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_RECORD_VALUE);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0x5678);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T6.29: test_cmd_get_record_children
|
||||
*
|
||||
* Verify cmd 0x46 format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_cmd_get_record_children (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_get_record_children (0x1234, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_GET_RECORD_CHILDREN);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0x1234);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* Command builder tests */
|
||||
g_test_add_func ("/validity/db/cmd_db_info", test_cmd_db_info);
|
||||
g_test_add_func ("/validity/db/cmd_get_user_storage", test_cmd_get_user_storage);
|
||||
g_test_add_func ("/validity/db/cmd_get_user_storage_null_name", test_cmd_get_user_storage_null_name);
|
||||
g_test_add_func ("/validity/db/cmd_get_user", test_cmd_get_user);
|
||||
g_test_add_func ("/validity/db/cmd_lookup_user", test_cmd_lookup_user);
|
||||
g_test_add_func ("/validity/db/cmd_new_record", test_cmd_new_record);
|
||||
g_test_add_func ("/validity/db/cmd_del_record", test_cmd_del_record);
|
||||
g_test_add_func ("/validity/db/cmd_create_enrollment", test_cmd_create_enrollment);
|
||||
g_test_add_func ("/validity/db/cmd_enrollment_update_start", test_cmd_enrollment_update_start);
|
||||
g_test_add_func ("/validity/db/cmd_enrollment_update", test_cmd_enrollment_update);
|
||||
g_test_add_func ("/validity/db/cmd_match_finger", test_cmd_match_finger);
|
||||
g_test_add_func ("/validity/db/cmd_get_match_result", test_cmd_get_match_result);
|
||||
g_test_add_func ("/validity/db/cmd_match_cleanup", test_cmd_match_cleanup);
|
||||
g_test_add_func ("/validity/db/cmd_get_prg_status", test_cmd_get_prg_status);
|
||||
g_test_add_func ("/validity/db/cmd_capture_stop", test_cmd_capture_stop);
|
||||
g_test_add_func ("/validity/db/cmd_call_cleanups", test_cmd_call_cleanups);
|
||||
g_test_add_func ("/validity/db/cmd_get_record_value", test_cmd_get_record_value);
|
||||
g_test_add_func ("/validity/db/cmd_get_record_children", test_cmd_get_record_children);
|
||||
|
||||
/* Response parser tests */
|
||||
g_test_add_func ("/validity/db/parse_info", test_parse_db_info);
|
||||
g_test_add_func ("/validity/db/parse_info_too_short", test_parse_db_info_too_short);
|
||||
g_test_add_func ("/validity/db/parse_user_storage", test_parse_user_storage);
|
||||
g_test_add_func ("/validity/db/parse_user", test_parse_user);
|
||||
g_test_add_func ("/validity/db/parse_new_record_id", test_parse_new_record_id);
|
||||
g_test_add_func ("/validity/db/parse_new_record_id_too_short", test_parse_new_record_id_too_short);
|
||||
g_test_add_func ("/validity/db/parse_record_value", test_parse_record_value);
|
||||
g_test_add_func ("/validity/db/parse_record_children", test_parse_record_children);
|
||||
|
||||
/* Identity and finger data tests */
|
||||
g_test_add_func ("/validity/db/build_identity", test_build_identity);
|
||||
g_test_add_func ("/validity/db/build_finger_data", test_build_finger_data);
|
||||
|
||||
/* Blob accessor test */
|
||||
g_test_add_func ("/validity/db/write_enable_blob", test_db_write_enable_blob);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
/*
|
||||
* Unit tests for enrollment response parsing
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "drivers/validity/validity.h"
|
||||
|
||||
/* ================================================================
|
||||
* Helper: build a tagged block
|
||||
* [tag:2LE][len:2LE][padding:MAGIC_LEN][payload:len]
|
||||
* Total block size = 4 + MAGIC_LEN + len = MAGIC_LEN + len + 4
|
||||
* Wait — re-read the parser:
|
||||
* tag(2LE) | len(2LE) => block_size = MAGIC_LEN + len
|
||||
* so the full block is [tag:2][len:2] + body[MAGIC_LEN + len]
|
||||
* No — looking at the code: pos + 4 reads tag+len, then
|
||||
* block_size = MAGIC_LEN + len, and the block starts at data[pos].
|
||||
* Template: data[pos .. pos + block_size].
|
||||
* Header: data[pos + MAGIC_LEN .. pos + MAGIC_LEN + len].
|
||||
* Advance: pos += block_size.
|
||||
*
|
||||
* Actually re-reading more carefully:
|
||||
* tag = data[pos], len = data[pos+2]
|
||||
* block_size = MAGIC_LEN + len
|
||||
* template = data[pos .. pos + block_size]
|
||||
* So the 4 bytes of tag+len are INSIDE the block_size.
|
||||
* MAGIC_LEN = 0x38 = 56 which is > 4, so tag+len fit inside.
|
||||
*
|
||||
* To build test data: write tag(2LE) at offset 0, len(2LE) at
|
||||
* offset 2, then (MAGIC_LEN - 4) padding bytes, then len payload bytes.
|
||||
* Total = MAGIC_LEN + len.
|
||||
* ================================================================ */
|
||||
static guint8 *
|
||||
build_block (guint16 tag, const guint8 *payload, guint16 payload_len,
|
||||
gsize *out_len)
|
||||
{
|
||||
gsize block_size = ENROLLMENT_MAGIC_LEN + payload_len;
|
||||
guint8 *buf = g_malloc0 (block_size);
|
||||
|
||||
FP_WRITE_UINT16_LE (buf, tag);
|
||||
FP_WRITE_UINT16_LE (buf + 2, payload_len);
|
||||
|
||||
if (payload && payload_len > 0)
|
||||
memcpy (buf + ENROLLMENT_MAGIC_LEN, payload, payload_len);
|
||||
|
||||
*out_len = block_size;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Wrap raw block data with the 2-byte declared_len prefix the parser expects:
|
||||
* [declared_len:2LE][blocks...]
|
||||
* declared_len = blocks_len (total size of all concatenated blocks). */
|
||||
static guint8 *
|
||||
wrap_response (const guint8 *blocks, gsize blocks_len, gsize *out_len)
|
||||
{
|
||||
*out_len = 2 + blocks_len;
|
||||
|
||||
guint8 *buf = g_malloc (*out_len);
|
||||
|
||||
FP_WRITE_UINT16_LE (buf, (guint16) blocks_len);
|
||||
if (blocks && blocks_len > 0)
|
||||
memcpy (buf + 2, blocks, blocks_len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.1: parse empty data — returns TRUE, all fields NULL
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_empty (void)
|
||||
{
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (NULL, 0, &result);
|
||||
|
||||
/* Empty data (len < 2) → parser returns FALSE */
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.2: parse single template block (tag=0)
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_template_block (void)
|
||||
{
|
||||
guint8 payload[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
gsize block_len;
|
||||
g_autofree guint8 *block = build_block (0, payload, sizeof (payload),
|
||||
&block_len);
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (block, block_len, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (result.template_data);
|
||||
g_assert_cmpuint (result.template_len, ==, block_len);
|
||||
g_assert_null (result.header);
|
||||
g_assert_null (result.tid);
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.3: parse header block (tag=1)
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_header_block (void)
|
||||
{
|
||||
guint8 payload[] = { 0x01, 0x02, 0x03 };
|
||||
gsize block_len;
|
||||
g_autofree guint8 *block = build_block (1, payload, sizeof (payload),
|
||||
&block_len);
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (block, block_len, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (result.header);
|
||||
g_assert_cmpuint (result.header_len, ==, sizeof (payload));
|
||||
g_assert_cmpmem (result.header, result.header_len, payload, sizeof (payload));
|
||||
g_assert_null (result.template_data);
|
||||
g_assert_null (result.tid);
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.4: parse tid block (tag=3) — signals enrollment complete
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_tid_block (void)
|
||||
{
|
||||
guint8 payload[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
||||
gsize block_len;
|
||||
g_autofree guint8 *block = build_block (3, payload, sizeof (payload),
|
||||
&block_len);
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (block, block_len, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (result.tid);
|
||||
g_assert_cmpuint (result.tid_len, ==, sizeof (payload));
|
||||
g_assert_cmpmem (result.tid, result.tid_len, payload, sizeof (payload));
|
||||
g_assert_null (result.template_data);
|
||||
g_assert_null (result.header);
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.5: parse multiple blocks — template + header + tid
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_multiple_blocks (void)
|
||||
{
|
||||
guint8 tmpl_payload[] = { 0x11, 0x22 };
|
||||
guint8 hdr_payload[] = { 0x33, 0x44, 0x55 };
|
||||
guint8 tid_payload[] = { 0x66 };
|
||||
|
||||
gsize tmpl_len, hdr_len, tid_len;
|
||||
g_autofree guint8 *tmpl = build_block (0, tmpl_payload,
|
||||
sizeof (tmpl_payload), &tmpl_len);
|
||||
g_autofree guint8 *hdr = build_block (1, hdr_payload,
|
||||
sizeof (hdr_payload), &hdr_len);
|
||||
g_autofree guint8 *tid = build_block (3, tid_payload,
|
||||
sizeof (tid_payload), &tid_len);
|
||||
|
||||
/* Concatenate all three blocks, then wrap with length prefix */
|
||||
gsize blocks_total = tmpl_len + hdr_len + tid_len;
|
||||
g_autofree guint8 *blocks = g_malloc (blocks_total);
|
||||
|
||||
memcpy (blocks, tmpl, tmpl_len);
|
||||
memcpy (blocks + tmpl_len, hdr, hdr_len);
|
||||
memcpy (blocks + tmpl_len + hdr_len, tid, tid_len);
|
||||
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (blocks, blocks_total, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_nonnull (result.template_data);
|
||||
g_assert_nonnull (result.header);
|
||||
g_assert_nonnull (result.tid);
|
||||
g_assert_cmpuint (result.template_len, ==, tmpl_len);
|
||||
g_assert_cmpuint (result.header_len, ==, sizeof (hdr_payload));
|
||||
g_assert_cmpuint (result.tid_len, ==, sizeof (tid_payload));
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.6: parse truncated data — stops before reading past buffer
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_truncated (void)
|
||||
{
|
||||
/* Build a response where the declared length is consistent with data_len
|
||||
* but the block content is too short for a full block to be parsed.
|
||||
* declared_len = 6, so data = [06 00][tag:2][len:2][2 more bytes]
|
||||
* The block_size = MAGIC_LEN + len will exceed 8 for any len > 0,
|
||||
* so the parser's "pos + block_size > data_len" check will skip it. */
|
||||
guint8 data[8];
|
||||
|
||||
FP_WRITE_UINT16_LE (data, 6); /* declared_len = 6 */
|
||||
FP_WRITE_UINT16_LE (data + 2, 0); /* tag = 0 (template) */
|
||||
FP_WRITE_UINT16_LE (data + 4, 10); /* len = 10 → block_size = MAGIC_LEN + 10 > 8 */
|
||||
data[6] = 0;
|
||||
data[7] = 0;
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, sizeof (data), &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
/* No fields should be populated since the block was truncated */
|
||||
g_assert_null (result.template_data);
|
||||
g_assert_null (result.header);
|
||||
g_assert_null (result.tid);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.7: parse unknown tag — silently skipped
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_unknown_tag (void)
|
||||
{
|
||||
guint8 payload[] = { 0x99 };
|
||||
gsize block_len;
|
||||
g_autofree guint8 *block = build_block (42, payload, sizeof (payload),
|
||||
&block_len);
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (block, block_len, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_null (result.template_data);
|
||||
g_assert_null (result.header);
|
||||
g_assert_null (result.tid);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.8: result_clear — frees and zeroes
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_result_clear (void)
|
||||
{
|
||||
EnrollmentUpdateResult result;
|
||||
|
||||
result.header = g_malloc (10);
|
||||
result.header_len = 10;
|
||||
result.template_data = g_malloc (20);
|
||||
result.template_len = 20;
|
||||
result.tid = g_malloc (5);
|
||||
result.tid_len = 5;
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
|
||||
g_assert_null (result.header);
|
||||
g_assert_null (result.template_data);
|
||||
g_assert_null (result.tid);
|
||||
g_assert_cmpuint (result.header_len, ==, 0);
|
||||
g_assert_cmpuint (result.template_len, ==, 0);
|
||||
g_assert_cmpuint (result.tid_len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T8.9: parse zero-length payload — tag present but no data
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_zero_length_payload (void)
|
||||
{
|
||||
gsize block_len;
|
||||
g_autofree guint8 *block = build_block (1, NULL, 0, &block_len);
|
||||
gsize resp_len;
|
||||
g_autofree guint8 *data = wrap_response (block, block_len, &resp_len);
|
||||
|
||||
EnrollmentUpdateResult result;
|
||||
gboolean ok = parse_enrollment_update_response (data, resp_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
/* Tag 1 with len=0: header should be NULL (len > 0 check in parser) */
|
||||
g_assert_null (result.header);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/enroll/parse-empty",
|
||||
test_parse_empty);
|
||||
g_test_add_func ("/validity/enroll/parse-template-block",
|
||||
test_parse_template_block);
|
||||
g_test_add_func ("/validity/enroll/parse-header-block",
|
||||
test_parse_header_block);
|
||||
g_test_add_func ("/validity/enroll/parse-tid-block",
|
||||
test_parse_tid_block);
|
||||
g_test_add_func ("/validity/enroll/parse-multiple-blocks",
|
||||
test_parse_multiple_blocks);
|
||||
g_test_add_func ("/validity/enroll/parse-truncated",
|
||||
test_parse_truncated);
|
||||
g_test_add_func ("/validity/enroll/parse-unknown-tag",
|
||||
test_parse_unknown_tag);
|
||||
g_test_add_func ("/validity/enroll/result-clear",
|
||||
test_result_clear);
|
||||
g_test_add_func ("/validity/enroll/parse-zero-length-payload",
|
||||
test_parse_zero_length_payload);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,644 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity firmware extension upload functions
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
|
||||
#include "drivers/validity/validity_fwext.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* T3.1: test_fw_info_parse_present
|
||||
*
|
||||
* Verify that a valid GET_FW_INFO response (status=OK) with 1 module
|
||||
* is parsed correctly into ValidityFwInfo.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_present (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
/* Build a synthetic GET_FW_INFO response:
|
||||
* major(2) + minor(2) + modcnt(2) + buildtime(4) = 10 header bytes
|
||||
* + 1 module * 12 bytes = 12
|
||||
* Total = 22 bytes (data after 2-byte status, which is stripped) */
|
||||
guint8 data[22];
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
/* major = 6 */
|
||||
data[0] = 6;
|
||||
data[1] = 0;
|
||||
/* minor = 7 */
|
||||
data[2] = 7;
|
||||
data[3] = 0;
|
||||
/* module_count = 1 */
|
||||
data[4] = 1;
|
||||
data[5] = 0;
|
||||
/* buildtime = 0x12345678 LE */
|
||||
data[6] = 0x78;
|
||||
data[7] = 0x56;
|
||||
data[8] = 0x34;
|
||||
data[9] = 0x12;
|
||||
/* Module 0: type=3, subtype=4, major=1, minor=2, size=0x1000 */
|
||||
data[10] = 3;
|
||||
data[11] = 0;
|
||||
data[12] = 4;
|
||||
data[13] = 0;
|
||||
data[14] = 1;
|
||||
data[15] = 0;
|
||||
data[16] = 2;
|
||||
data[17] = 0;
|
||||
data[18] = 0x00;
|
||||
data[19] = 0x10;
|
||||
data[20] = 0x00;
|
||||
data[21] = 0x00;
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (data, sizeof (data),
|
||||
VCSFW_STATUS_OK, &info);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_true (info.loaded);
|
||||
g_assert_cmpuint (info.major, ==, 6);
|
||||
g_assert_cmpuint (info.minor, ==, 7);
|
||||
g_assert_cmpuint (info.module_count, ==, 1);
|
||||
g_assert_cmpuint (info.buildtime, ==, 0x12345678);
|
||||
g_assert_cmpuint (info.modules[0].type, ==, 3);
|
||||
g_assert_cmpuint (info.modules[0].subtype, ==, 4);
|
||||
g_assert_cmpuint (info.modules[0].major, ==, 1);
|
||||
g_assert_cmpuint (info.modules[0].minor, ==, 2);
|
||||
g_assert_cmpuint (info.modules[0].size, ==, 0x1000);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2: test_fw_info_parse_absent
|
||||
*
|
||||
* Verify that status VCSFW_STATUS_NO_FW sets loaded=FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_absent (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (NULL, 0,
|
||||
VCSFW_STATUS_NO_FW, &info);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_false (info.loaded);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2b: test_fw_info_parse_unknown_status
|
||||
*
|
||||
* Verify that an unexpected status returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_unknown_status (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
|
||||
g_test_expect_message ("libfprint-validity", G_LOG_LEVEL_WARNING,
|
||||
"*unexpected status*");
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (NULL, 0, 0x9999, &info);
|
||||
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_false (ok);
|
||||
g_assert_false (info.loaded);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.2c: test_fw_info_parse_truncated
|
||||
*
|
||||
* Verify that status=OK but data too short returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_info_parse_truncated (void)
|
||||
{
|
||||
ValidityFwInfo info;
|
||||
guint8 data[5] = { 0 };
|
||||
|
||||
g_test_expect_message ("libfprint-validity", G_LOG_LEVEL_WARNING,
|
||||
"*too short*");
|
||||
|
||||
gboolean ok = validity_fwext_parse_fw_info (data, sizeof (data),
|
||||
VCSFW_STATUS_OK, &info);
|
||||
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3: test_xpfwext_file_parse
|
||||
*
|
||||
* Create a synthetic .xpfwext file in a temp dir and verify parsing.
|
||||
* Format: header + 0x1A + payload + 256-byte signature
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_parse (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "test.xpfwext", NULL);
|
||||
|
||||
/* Build file: "HDR" + 0x1A + 8 bytes payload + 256 bytes sig */
|
||||
gsize header_len = 4; /* "HDR" + 0x1A */
|
||||
gsize payload_len = 8;
|
||||
gsize sig_len = 256;
|
||||
gsize total = header_len + payload_len + sig_len;
|
||||
|
||||
guint8 *content = g_malloc0 (total);
|
||||
|
||||
content[0] = 'H';
|
||||
content[1] = 'D';
|
||||
content[2] = 'R';
|
||||
content[3] = 0x1A;
|
||||
|
||||
/* Payload: 0x01..0x08 */
|
||||
for (gsize i = 0; i < payload_len; i++)
|
||||
content[header_len + i] = (guint8) (i + 1);
|
||||
|
||||
/* Signature: 0xAA repeated */
|
||||
memset (content + header_len + payload_len, 0xAA, sig_len);
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, total, &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ok);
|
||||
|
||||
g_assert_cmpuint (fwext.payload_len, ==, payload_len);
|
||||
|
||||
/* Check payload content */
|
||||
for (gsize i = 0; i < payload_len; i++)
|
||||
g_assert_cmpuint (fwext.payload[i], ==, i + 1);
|
||||
|
||||
/* Check signature */
|
||||
for (gsize i = 0; i < sig_len; i++)
|
||||
g_assert_cmpuint (fwext.signature[i], ==, 0xAA);
|
||||
|
||||
validity_fwext_file_clear (&fwext);
|
||||
g_assert_null (fwext.payload);
|
||||
g_assert_cmpuint (fwext.payload_len, ==, 0);
|
||||
|
||||
/* Cleanup */
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (content);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3b: test_xpfwext_file_no_delimiter
|
||||
*
|
||||
* Verify that a file without 0x1A delimiter fails to parse.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_no_delimiter (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "bad.xpfwext", NULL);
|
||||
|
||||
/* All 0xFF — no 0x1A delimiter */
|
||||
guint8 content[300];
|
||||
|
||||
memset (content, 0xFF, sizeof (content));
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, sizeof (content), &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_false (ok);
|
||||
g_assert_nonnull (error);
|
||||
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.3c: test_xpfwext_file_too_short
|
||||
*
|
||||
* Verify that a file with valid header but data shorter than
|
||||
* signature size fails.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_xpfwext_file_too_short (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
ValidityFwextFile fwext;
|
||||
gchar *tmpdir;
|
||||
g_autofree gchar *path = NULL;
|
||||
|
||||
tmpdir = g_dir_make_tmp ("fwext-test-XXXXXX", &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
path = g_build_filename (tmpdir, "short.xpfwext", NULL);
|
||||
|
||||
/* "X" + 0x1A + 10 bytes (< 257 needed for sig + 1 byte payload) */
|
||||
guint8 content[12];
|
||||
|
||||
content[0] = 'X';
|
||||
content[1] = 0x1A;
|
||||
memset (content + 2, 0, 10);
|
||||
|
||||
g_file_set_contents (path, (gchar *) content, sizeof (content), &error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
gboolean ok = validity_fwext_load_file (path, &fwext, &error);
|
||||
g_assert_false (ok);
|
||||
g_assert_nonnull (error);
|
||||
|
||||
g_unlink (path);
|
||||
g_rmdir (tmpdir);
|
||||
g_free (tmpdir);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.4: test_flash_write_cmd_format
|
||||
*
|
||||
* Verify that build_write_flash produces the correct wire format:
|
||||
* [0x41, partition, 1, 0, 0, offset_LE32, len_LE32, data...]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_write_cmd_format (void)
|
||||
{
|
||||
guint8 payload[] = { 0xDE, 0xAD, 0xBE, 0xEF };
|
||||
guint8 cmd[13 + sizeof (payload)];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_flash (2, 0x1000, payload, sizeof (payload),
|
||||
cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 13 + sizeof (payload));
|
||||
g_assert_cmpuint (cmd[0], ==, 0x41); /* WRITE_FLASH command */
|
||||
g_assert_cmpuint (cmd[1], ==, 2); /* partition */
|
||||
g_assert_cmpuint (cmd[2], ==, 1); /* flag */
|
||||
g_assert_cmpuint (cmd[3], ==, 0); /* reserved low */
|
||||
g_assert_cmpuint (cmd[4], ==, 0); /* reserved high */
|
||||
|
||||
/* offset = 0x1000 LE */
|
||||
g_assert_cmpuint (cmd[5], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[6], ==, 0x10);
|
||||
g_assert_cmpuint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[8], ==, 0x00);
|
||||
|
||||
/* length = 4 LE */
|
||||
g_assert_cmpuint (cmd[9], ==, 0x04);
|
||||
g_assert_cmpuint (cmd[10], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[11], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[12], ==, 0x00);
|
||||
|
||||
/* data */
|
||||
g_assert_cmpmem (cmd + 13, sizeof (payload), payload, sizeof (payload));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.5: test_fw_sig_cmd_format
|
||||
*
|
||||
* Verify that build_write_fw_sig produces the correct wire format:
|
||||
* [0x42, partition, 0, len_LE16, signature...]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_fw_sig_cmd_format (void)
|
||||
{
|
||||
guint8 sig[256];
|
||||
guint8 cmd[5 + 256];
|
||||
gsize cmd_len;
|
||||
|
||||
memset (sig, 0xBB, sizeof (sig));
|
||||
|
||||
validity_fwext_build_write_fw_sig (2, sig, sizeof (sig), cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 5 + 256);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x42); /* WRITE_FW_SIG command */
|
||||
g_assert_cmpuint (cmd[1], ==, 2); /* partition */
|
||||
g_assert_cmpuint (cmd[2], ==, 0); /* reserved */
|
||||
|
||||
/* sig length = 256 LE */
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x01);
|
||||
|
||||
g_assert_cmpmem (cmd + 5, 256, sig, 256);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.6: test_chunk_iteration
|
||||
*
|
||||
* Verify that building write_flash with increasing offsets covers
|
||||
* the entire payload. Simulate the chunk loop used by the SSM.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_chunk_iteration (void)
|
||||
{
|
||||
gsize payload_len = 0x2800; /* 10 KB = 2.5 chunks of 4 KB */
|
||||
gsize write_offset = 0;
|
||||
guint chunk_count = 0;
|
||||
const gsize CHUNK_SIZE = 0x1000;
|
||||
|
||||
while (write_offset < payload_len)
|
||||
{
|
||||
gsize remaining = payload_len - write_offset;
|
||||
gsize chunk_size = MIN (remaining, CHUNK_SIZE);
|
||||
|
||||
g_assert_cmpuint (chunk_size, >, 0);
|
||||
g_assert_cmpuint (chunk_size, <=, CHUNK_SIZE);
|
||||
|
||||
write_offset += chunk_size;
|
||||
chunk_count++;
|
||||
}
|
||||
|
||||
g_assert_cmpuint (write_offset, ==, payload_len);
|
||||
g_assert_cmpuint (chunk_count, ==, 3); /* 4096 + 4096 + 2048 */
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.7: test_hw_reg_cmd_format
|
||||
*
|
||||
* Verify WRITE_HW_REG32 and READ_HW_REG32 command formats.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hw_reg_write_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[10];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_write_hw_reg32 (0x8000205C, 7, cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 10);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x08); /* WRITE_HW_REG32 command */
|
||||
|
||||
/* address = 0x8000205C LE */
|
||||
g_assert_cmpuint (cmd[1], ==, 0x5C);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x20);
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x80);
|
||||
|
||||
/* value = 7 LE */
|
||||
g_assert_cmpuint (cmd[5], ==, 0x07);
|
||||
g_assert_cmpuint (cmd[6], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[8], ==, 0x00);
|
||||
|
||||
/* size = 4 */
|
||||
g_assert_cmpuint (cmd[9], ==, 4);
|
||||
}
|
||||
|
||||
static void
|
||||
test_hw_reg_read_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[6];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_read_hw_reg32 (0x80002080, cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 6);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x07); /* READ_HW_REG32 command */
|
||||
|
||||
/* address = 0x80002080 LE */
|
||||
g_assert_cmpuint (cmd[1], ==, 0x80);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x20);
|
||||
g_assert_cmpuint (cmd[3], ==, 0x00);
|
||||
g_assert_cmpuint (cmd[4], ==, 0x80);
|
||||
|
||||
/* size = 4 */
|
||||
g_assert_cmpuint (cmd[5], ==, 4);
|
||||
}
|
||||
|
||||
static void
|
||||
test_hw_reg_read_parse (void)
|
||||
{
|
||||
guint32 value;
|
||||
guint8 data[] = { 0x02, 0x00, 0x00, 0x00 };
|
||||
|
||||
gboolean ok = validity_fwext_parse_read_hw_reg32 (data, sizeof (data),
|
||||
&value);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (value, ==, 2);
|
||||
|
||||
/* Too short should fail */
|
||||
ok = validity_fwext_parse_read_hw_reg32 (data, 3, &value);
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.8: test_firmware_filename
|
||||
*
|
||||
* Verify firmware filename mapping for known PIDs.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_firmware_filename (void)
|
||||
{
|
||||
const gchar *name;
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x06cb, 0x009a);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x0090);
|
||||
g_assert_cmpstr (name, ==, "6_07f_Lenovo.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x0097);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
name = validity_fwext_get_firmware_name (0x138a, 0x009d);
|
||||
g_assert_cmpstr (name, ==, "6_07f_lenovo_mis_qm.xpfwext");
|
||||
|
||||
/* Unknown PID should return NULL */
|
||||
name = validity_fwext_get_firmware_name (0x1234, 0x5678);
|
||||
g_assert_null (name);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.9: test_missing_firmware_file
|
||||
*
|
||||
* Verify that find_firmware returns an error when the file is not
|
||||
* found in any search path.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_missing_firmware_file (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
/* This should fail since firmware files aren't installed in CI */
|
||||
g_autofree gchar *path = validity_fwext_find_firmware (0x06cb, 0x009a,
|
||||
&error);
|
||||
|
||||
/* It either found a file or returned an error — both are valid.
|
||||
* In CI, it should be an error. On a real system, it might succeed. */
|
||||
if (path == NULL)
|
||||
{
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_error_matches (error, FP_DEVICE_ERROR,
|
||||
FP_DEVICE_ERROR_DATA_NOT_FOUND));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (g_file_test (path, G_FILE_TEST_IS_REGULAR));
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.9b: test_unsupported_pid_firmware
|
||||
*
|
||||
* Verify that find_firmware for an unknown PID returns NOT_SUPPORTED.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_unsupported_pid_firmware (void)
|
||||
{
|
||||
g_autoptr(GError) error = NULL;
|
||||
g_autofree gchar *path = validity_fwext_find_firmware (0x1234, 0x5678,
|
||||
&error);
|
||||
|
||||
g_assert_null (path);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_error_matches (error, FP_DEVICE_ERROR,
|
||||
FP_DEVICE_ERROR_NOT_SUPPORTED));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.10: test_db_write_enable_blob
|
||||
*
|
||||
* Verify that db_write_enable blob is returned for supported PID
|
||||
* and NULL for unsupported PID.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_db_write_enable_blob (void)
|
||||
{
|
||||
gsize len;
|
||||
const guint8 *blob;
|
||||
|
||||
blob = validity_fwext_get_db_write_enable (0x06cb, 0x009a, &len);
|
||||
g_assert_nonnull (blob);
|
||||
g_assert_cmpuint (len, ==, 3621);
|
||||
|
||||
blob = validity_fwext_get_db_write_enable (0x1234, 0x5678, &len);
|
||||
g_assert_null (blob);
|
||||
g_assert_cmpuint (len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T3.11: test_reboot_cmd_format
|
||||
*
|
||||
* Verify reboot command: 0x05, 0x02, 0x00
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_reboot_cmd_format (void)
|
||||
{
|
||||
guint8 cmd[3];
|
||||
gsize cmd_len;
|
||||
|
||||
validity_fwext_build_reboot (cmd, &cmd_len);
|
||||
|
||||
g_assert_cmpuint (cmd_len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, 0x05);
|
||||
g_assert_cmpuint (cmd[1], ==, 0x02);
|
||||
g_assert_cmpuint (cmd[2], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: fwext_file_clear on already-cleared struct
|
||||
*
|
||||
* Double-free guard.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_file_clear_idempotent (void)
|
||||
{
|
||||
ValidityFwextFile fwext = { 0 };
|
||||
|
||||
/* Clear an empty struct — should not crash */
|
||||
validity_fwext_file_clear (&fwext);
|
||||
validity_fwext_file_clear (&fwext);
|
||||
|
||||
g_assert_null (fwext.payload);
|
||||
g_assert_cmpuint (fwext.payload_len, ==, 0);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* Firmware info parsing */
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-present",
|
||||
test_fw_info_parse_present);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-absent",
|
||||
test_fw_info_parse_absent);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-unknown-status",
|
||||
test_fw_info_parse_unknown_status);
|
||||
g_test_add_func ("/validity/fwext/fw-info/parse-truncated",
|
||||
test_fw_info_parse_truncated);
|
||||
|
||||
/* File parsing */
|
||||
g_test_add_func ("/validity/fwext/file/parse",
|
||||
test_xpfwext_file_parse);
|
||||
g_test_add_func ("/validity/fwext/file/no-delimiter",
|
||||
test_xpfwext_file_no_delimiter);
|
||||
g_test_add_func ("/validity/fwext/file/too-short",
|
||||
test_xpfwext_file_too_short);
|
||||
g_test_add_func ("/validity/fwext/file/clear-idempotent",
|
||||
test_file_clear_idempotent);
|
||||
|
||||
/* Command format */
|
||||
g_test_add_func ("/validity/fwext/cmd/write-flash",
|
||||
test_flash_write_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/write-fw-sig",
|
||||
test_fw_sig_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/write-hw-reg",
|
||||
test_hw_reg_write_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/read-hw-reg",
|
||||
test_hw_reg_read_cmd_format);
|
||||
g_test_add_func ("/validity/fwext/cmd/read-hw-reg-parse",
|
||||
test_hw_reg_read_parse);
|
||||
g_test_add_func ("/validity/fwext/cmd/reboot",
|
||||
test_reboot_cmd_format);
|
||||
|
||||
/* Chunk iteration */
|
||||
g_test_add_func ("/validity/fwext/chunk-iteration",
|
||||
test_chunk_iteration);
|
||||
|
||||
/* Firmware filename mapping */
|
||||
g_test_add_func ("/validity/fwext/firmware-name",
|
||||
test_firmware_filename);
|
||||
g_test_add_func ("/validity/fwext/find-firmware/missing",
|
||||
test_missing_firmware_file);
|
||||
g_test_add_func ("/validity/fwext/find-firmware/unsupported-pid",
|
||||
test_unsupported_pid_firmware);
|
||||
|
||||
/* Blob lookup */
|
||||
g_test_add_func ("/validity/fwext/db-write-enable",
|
||||
test_db_write_enable_blob);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity HAL (device descriptor lookup and flash layout)
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "drivers/validity/validity.h"
|
||||
#include "drivers/validity/validity_hal.h"
|
||||
|
||||
/* ================================================================
|
||||
* T7.1: HAL lookup by device type — all valid types return non-NULL
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_lookup_all_types (void)
|
||||
{
|
||||
const guint types[] = { VALIDITY_DEV_90, VALIDITY_DEV_97,
|
||||
VALIDITY_DEV_9A, VALIDITY_DEV_9D };
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (types); i++)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (types[i]);
|
||||
g_assert_nonnull (desc);
|
||||
g_assert_cmpuint (desc->vid, >, 0);
|
||||
g_assert_cmpuint (desc->pid, >, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.2: HAL lookup by PID — all supported VID/PID combos
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_lookup_by_pid (void)
|
||||
{
|
||||
/* All 4 supported devices */
|
||||
struct { guint16 vid;
|
||||
guint16 pid;
|
||||
} devices[] = {
|
||||
{ 0x138a, 0x0090 },
|
||||
{ 0x138a, 0x0097 },
|
||||
{ 0x06cb, 0x009a },
|
||||
{ 0x138a, 0x009d },
|
||||
};
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (devices); i++)
|
||||
{
|
||||
const ValidityDeviceDesc *desc =
|
||||
validity_hal_device_lookup_by_pid (devices[i].vid, devices[i].pid);
|
||||
g_assert_nonnull (desc);
|
||||
g_assert_cmpuint (desc->vid, ==, devices[i].vid);
|
||||
g_assert_cmpuint (desc->pid, ==, devices[i].pid);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.3: HAL lookup — invalid type returns NULL
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_lookup_invalid (void)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (99);
|
||||
|
||||
g_assert_null (desc);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.4: HAL lookup by PID — unknown PID returns NULL
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_lookup_by_pid_invalid (void)
|
||||
{
|
||||
const ValidityDeviceDesc *desc =
|
||||
validity_hal_device_lookup_by_pid (0x1234, 0x5678);
|
||||
|
||||
g_assert_null (desc);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.5: All devices have non-empty blobs
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_blobs_present (void)
|
||||
{
|
||||
const guint types[] = { VALIDITY_DEV_90, VALIDITY_DEV_97,
|
||||
VALIDITY_DEV_9A, VALIDITY_DEV_9D };
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (types); i++)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (types[i]);
|
||||
g_assert_nonnull (desc);
|
||||
|
||||
/* init_hardcoded must be present for all */
|
||||
g_assert_nonnull (desc->init_hardcoded);
|
||||
g_assert_cmpuint (desc->init_hardcoded_len, >, 0);
|
||||
|
||||
/* reset_blob must be present for all */
|
||||
g_assert_nonnull (desc->reset_blob);
|
||||
g_assert_cmpuint (desc->reset_blob_len, >, 0);
|
||||
|
||||
/* db_write_enable must be present for all */
|
||||
g_assert_nonnull (desc->db_write_enable);
|
||||
g_assert_cmpuint (desc->db_write_enable_len, >, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.6: PID 0090 has smaller db partition and no clean_slate blob
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_pid_0090_specifics (void)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (VALIDITY_DEV_90);
|
||||
|
||||
g_assert_nonnull (desc);
|
||||
|
||||
/* 0090 has no init_hardcoded_clean_slate */
|
||||
g_assert_null (desc->init_clean_slate);
|
||||
g_assert_cmpuint (desc->init_clean_slate_len, ==, 0);
|
||||
|
||||
/* Flash layout should exist */
|
||||
g_assert_nonnull (desc->flash_layout);
|
||||
g_assert_cmpuint (desc->flash_layout->num_partitions, ==,
|
||||
VALIDITY_FLASH_NUM_PARTITIONS);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.7: Non-0090 devices have init_hardcoded_clean_slate
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_clean_slate_present (void)
|
||||
{
|
||||
const guint types[] = { VALIDITY_DEV_97, VALIDITY_DEV_9A, VALIDITY_DEV_9D };
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (types); i++)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (types[i]);
|
||||
g_assert_nonnull (desc);
|
||||
g_assert_nonnull (desc->init_clean_slate);
|
||||
g_assert_cmpuint (desc->init_clean_slate_len, >, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.8: Flash layout has valid partition table
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_flash_layout (void)
|
||||
{
|
||||
const guint types[] = { VALIDITY_DEV_90, VALIDITY_DEV_97,
|
||||
VALIDITY_DEV_9A, VALIDITY_DEV_9D };
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (types); i++)
|
||||
{
|
||||
const ValidityDeviceDesc *desc = validity_hal_device_lookup (types[i]);
|
||||
g_assert_nonnull (desc);
|
||||
g_assert_nonnull (desc->flash_layout);
|
||||
|
||||
const ValidityFlashLayout *layout = desc->flash_layout;
|
||||
g_assert_cmpuint (layout->num_partitions, ==,
|
||||
VALIDITY_FLASH_NUM_PARTITIONS);
|
||||
|
||||
/* Signature must be 256 bytes */
|
||||
g_assert_nonnull (layout->partition_sig);
|
||||
g_assert_cmpuint (layout->partition_sig_len, ==,
|
||||
VALIDITY_PARTITION_SIG_SIZE);
|
||||
|
||||
/* Verify partitions are ordered and non-overlapping */
|
||||
for (guint p = 0; p < layout->num_partitions; p++)
|
||||
{
|
||||
const ValidityPartition *part = &layout->partitions[p];
|
||||
g_assert_cmpuint (part->size, >, 0);
|
||||
|
||||
if (p > 0)
|
||||
{
|
||||
const ValidityPartition *prev = &layout->partitions[p - 1];
|
||||
g_assert_cmpuint (part->offset, >=,
|
||||
prev->offset + prev->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.9: Blob sizes match expected values from python-validity
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_blob_sizes (void)
|
||||
{
|
||||
const ValidityDeviceDesc *desc_9a =
|
||||
validity_hal_device_lookup (VALIDITY_DEV_9A);
|
||||
|
||||
g_assert_nonnull (desc_9a);
|
||||
|
||||
/* 009a blobs: init=581, clean_slate=741, reset=12037, dbe=3621 */
|
||||
g_assert_cmpuint (desc_9a->init_hardcoded_len, ==, 581);
|
||||
g_assert_cmpuint (desc_9a->init_clean_slate_len, ==, 741);
|
||||
g_assert_cmpuint (desc_9a->reset_blob_len, ==, 12037);
|
||||
g_assert_cmpuint (desc_9a->db_write_enable_len, ==, 3621);
|
||||
|
||||
const ValidityDeviceDesc *desc_90 =
|
||||
validity_hal_device_lookup (VALIDITY_DEV_90);
|
||||
g_assert_nonnull (desc_90);
|
||||
|
||||
/* 0090 blobs: init=485, no clean_slate, reset=11493, dbe=1765 */
|
||||
g_assert_cmpuint (desc_90->init_hardcoded_len, ==, 485);
|
||||
g_assert_cmpuint (desc_90->reset_blob_len, ==, 11493);
|
||||
g_assert_cmpuint (desc_90->db_write_enable_len, ==, 1765);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.10: Lookup consistency — by-type and by-PID return same pointer
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_hal_lookup_consistency (void)
|
||||
{
|
||||
const ValidityDeviceDesc *by_type =
|
||||
validity_hal_device_lookup (VALIDITY_DEV_9A);
|
||||
const ValidityDeviceDesc *by_pid =
|
||||
validity_hal_device_lookup_by_pid (0x06cb, 0x009a);
|
||||
|
||||
g_assert_true (by_type == by_pid);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/hal/lookup-all-types",
|
||||
test_hal_lookup_all_types);
|
||||
g_test_add_func ("/validity/hal/lookup-by-pid",
|
||||
test_hal_lookup_by_pid);
|
||||
g_test_add_func ("/validity/hal/lookup-invalid",
|
||||
test_hal_lookup_invalid);
|
||||
g_test_add_func ("/validity/hal/lookup-by-pid-invalid",
|
||||
test_hal_lookup_by_pid_invalid);
|
||||
g_test_add_func ("/validity/hal/blobs-present",
|
||||
test_hal_blobs_present);
|
||||
g_test_add_func ("/validity/hal/pid-0090-specifics",
|
||||
test_hal_pid_0090_specifics);
|
||||
g_test_add_func ("/validity/hal/clean-slate-present",
|
||||
test_hal_clean_slate_present);
|
||||
g_test_add_func ("/validity/hal/flash-layout",
|
||||
test_hal_flash_layout);
|
||||
g_test_add_func ("/validity/hal/blob-sizes",
|
||||
test_hal_blob_sizes);
|
||||
g_test_add_func ("/validity/hal/lookup-consistency",
|
||||
test_hal_lookup_consistency);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,507 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity pairing helper functions
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/param_build.h>
|
||||
#include <openssl/ec.h>
|
||||
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "drivers/validity/validity.h"
|
||||
#include "drivers/validity/validity_pair.h"
|
||||
#include "drivers/validity/validity_hal.h"
|
||||
|
||||
/* ================================================================
|
||||
* T7.11: parse_flash_info — valid response
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_flash_info_valid (void)
|
||||
{
|
||||
/* CMD 0x3e response format (after 2-byte status, already stripped):
|
||||
* [jid0:2LE][jid1:2LE][blocks:2LE][unknown0:2LE][blocksize:2LE]
|
||||
* [unknown1:2LE][pcnt:2LE] = 14 bytes minimum */
|
||||
guint8 data[14];
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
/* jid0=0x01, jid1=0x02, blocks=0x1000, unknown0=0, blocksize=0x100,
|
||||
* unknown1=0, pcnt=5 (5 partitions = device already paired) */
|
||||
FP_WRITE_UINT16_LE (data + 0, 0x0001); /* jid0 */
|
||||
FP_WRITE_UINT16_LE (data + 2, 0x0002); /* jid1 */
|
||||
FP_WRITE_UINT16_LE (data + 4, 0x1000); /* blocks */
|
||||
FP_WRITE_UINT16_LE (data + 6, 0x0000); /* unknown0 */
|
||||
FP_WRITE_UINT16_LE (data + 8, 0x0100); /* blocksize */
|
||||
FP_WRITE_UINT16_LE (data + 10, 0x0000); /* unknown1 */
|
||||
FP_WRITE_UINT16_LE (data + 12, 5); /* pcnt = 5 */
|
||||
|
||||
ValidityFlashIcParams ic;
|
||||
guint16 num_partitions;
|
||||
|
||||
gboolean ok = validity_pair_parse_flash_info (data, sizeof (data),
|
||||
&ic, &num_partitions);
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (num_partitions, ==, 5);
|
||||
g_assert_cmpuint (ic.size, ==, 0x1000 * 0x0100);
|
||||
g_assert_cmpuint (ic.sector_size, ==, 0x1000);
|
||||
g_assert_cmpuint (ic.sector_erase_cmd, ==, 0x20);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.12: parse_flash_info — zero partitions means needs pairing
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_flash_info_needs_pairing (void)
|
||||
{
|
||||
guint8 data[14];
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
FP_WRITE_UINT16_LE (data + 0, 0x0001);
|
||||
FP_WRITE_UINT16_LE (data + 2, 0x0002);
|
||||
FP_WRITE_UINT16_LE (data + 4, 0x0800);
|
||||
FP_WRITE_UINT16_LE (data + 8, 0x0200);
|
||||
FP_WRITE_UINT16_LE (data + 12, 0); /* 0 partitions */
|
||||
|
||||
ValidityFlashIcParams ic;
|
||||
guint16 num_partitions;
|
||||
|
||||
gboolean ok = validity_pair_parse_flash_info (data, sizeof (data),
|
||||
&ic, &num_partitions);
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (num_partitions, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.13: parse_flash_info — too short data fails
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_flash_info_too_short (void)
|
||||
{
|
||||
guint8 data[10]; /* less than 14 bytes */
|
||||
|
||||
memset (data, 0, sizeof (data));
|
||||
|
||||
ValidityFlashIcParams ic;
|
||||
guint16 num_partitions;
|
||||
|
||||
gboolean ok = validity_pair_parse_flash_info (data, sizeof (data),
|
||||
&ic, &num_partitions);
|
||||
g_assert_false (ok);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.14: serialize_partition — known output format
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_serialize_partition (void)
|
||||
{
|
||||
ValidityPartition part = {
|
||||
.id = 1,
|
||||
.type = 3,
|
||||
.access_lvl = 0x0002,
|
||||
.offset = 0x1000,
|
||||
.size = 0x8000,
|
||||
};
|
||||
|
||||
guint8 out[VALIDITY_PARTITION_ENTRY_SIZE];
|
||||
|
||||
validity_pair_serialize_partition (&part, out);
|
||||
|
||||
/* Check first 12 bytes: id(1) type(1) access_lvl(2LE) offset(4LE) size(4LE) */
|
||||
g_assert_cmpuint (out[0], ==, 1);
|
||||
g_assert_cmpuint (out[1], ==, 3);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (out + 2), ==, 0x0002);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (out + 4), ==, 0x1000);
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (out + 8), ==, 0x8000);
|
||||
|
||||
/* Bytes 12-15 should be zero */
|
||||
for (int i = 12; i < 16; i++)
|
||||
g_assert_cmpuint (out[i], ==, 0);
|
||||
|
||||
/* Bytes 16-47 = SHA-256 of the 12-byte entry */
|
||||
g_autoptr(GChecksum) checksum = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
g_checksum_update (checksum, out, 12);
|
||||
guint8 expected_hash[32];
|
||||
gsize hash_len = 32;
|
||||
g_checksum_get_digest (checksum, expected_hash, &hash_len);
|
||||
|
||||
g_assert_cmpmem (out + 16, 32, expected_hash, 32);
|
||||
|
||||
/* Total size must be VALIDITY_PARTITION_ENTRY_SIZE */
|
||||
g_assert_cmpuint (sizeof (out), ==, VALIDITY_PARTITION_ENTRY_SIZE);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.15: make_cert — produces 444-byte certificate
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_make_cert_size (void)
|
||||
{
|
||||
guint8 pub_x[32], pub_y[32];
|
||||
|
||||
RAND_bytes (pub_x, 32);
|
||||
RAND_bytes (pub_y, 32);
|
||||
|
||||
gsize cert_len;
|
||||
g_autofree guint8 *cert = validity_pair_make_cert (pub_x, pub_y, &cert_len);
|
||||
|
||||
g_assert_nonnull (cert);
|
||||
g_assert_cmpuint (cert_len, ==, VALIDITY_CLIENT_CERT_SIZE);
|
||||
|
||||
/* First 4 bytes should be 0x17 in LE */
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (cert), ==, 0x17);
|
||||
/* Bytes 4-7 should be 0x20 in LE */
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (cert + 4), ==, 0x20);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.16: make_cert — deterministic for same input
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_make_cert_deterministic (void)
|
||||
{
|
||||
/* Using fixed keys so signature is reproducible.
|
||||
* Note: ECDSA uses random k, so signatures differ — but the
|
||||
* structure should be consistent. Actually ECDSA is NOT deterministic
|
||||
* without RFC 6979, so we can only verify structure matches. */
|
||||
guint8 pub_x[32] = { 0x01 };
|
||||
guint8 pub_y[32] = { 0x02 };
|
||||
|
||||
gsize len1, len2;
|
||||
g_autofree guint8 *cert1 = validity_pair_make_cert (pub_x, pub_y, &len1);
|
||||
g_autofree guint8 *cert2 = validity_pair_make_cert (pub_x, pub_y, &len2);
|
||||
|
||||
g_assert_nonnull (cert1);
|
||||
g_assert_nonnull (cert2);
|
||||
g_assert_cmpuint (len1, ==, len2);
|
||||
g_assert_cmpuint (len1, ==, VALIDITY_CLIENT_CERT_SIZE);
|
||||
|
||||
/* Header and public key portion should be identical (first 184 bytes = cert body) */
|
||||
g_assert_cmpmem (cert1, 184, cert2, 184);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.17: encrypt_key — output blob has correct structure
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_key_structure (void)
|
||||
{
|
||||
guint8 priv[32], pub_x[32], pub_y[32];
|
||||
guint8 enc_key[32], val_key[32];
|
||||
|
||||
RAND_bytes (priv, 32);
|
||||
RAND_bytes (pub_x, 32);
|
||||
RAND_bytes (pub_y, 32);
|
||||
RAND_bytes (enc_key, 32);
|
||||
RAND_bytes (val_key, 32);
|
||||
|
||||
gsize blob_len;
|
||||
g_autofree guint8 *blob = validity_pair_encrypt_key (priv, pub_x, pub_y,
|
||||
enc_key, val_key,
|
||||
&blob_len);
|
||||
|
||||
g_assert_nonnull (blob);
|
||||
/* Blob format: 0x02(1) + IV(16) + ciphertext(112) + HMAC(32) = 161 */
|
||||
g_assert_cmpuint (blob_len, ==, 161);
|
||||
g_assert_cmpuint (blob[0], ==, VALIDITY_ENCRYPTED_KEY_PREFIX);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.18: encrypt_key — HMAC verification
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_key_hmac_valid (void)
|
||||
{
|
||||
guint8 priv[32], pub_x[32], pub_y[32];
|
||||
guint8 enc_key[32], val_key[32];
|
||||
|
||||
RAND_bytes (priv, 32);
|
||||
RAND_bytes (pub_x, 32);
|
||||
RAND_bytes (pub_y, 32);
|
||||
RAND_bytes (enc_key, 32);
|
||||
RAND_bytes (val_key, 32);
|
||||
|
||||
gsize blob_len;
|
||||
g_autofree guint8 *blob = validity_pair_encrypt_key (priv, pub_x, pub_y,
|
||||
enc_key, val_key,
|
||||
&blob_len);
|
||||
|
||||
g_assert_nonnull (blob);
|
||||
g_assert_cmpuint (blob_len, ==, 161);
|
||||
|
||||
/* Blob: [0x02][iv:16][ct:112][hmac:32]
|
||||
* HMAC is over iv+ct (bytes 1..128) */
|
||||
const guint8 *iv_ct = blob + 1;
|
||||
gsize iv_ct_len = 16 + 112;
|
||||
const guint8 *stored_hmac = blob + 1 + iv_ct_len;
|
||||
|
||||
guint8 computed_hmac[32];
|
||||
guint hmac_len = 32;
|
||||
HMAC (EVP_sha256 (), val_key, 32, iv_ct, iv_ct_len,
|
||||
computed_hmac, &hmac_len);
|
||||
|
||||
g_assert_cmpmem (stored_hmac, 32, computed_hmac, 32);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.19: build_partition_flash_cmd — valid output structure
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_partition_flash_cmd (void)
|
||||
{
|
||||
/* Use a real device descriptor for the flash layout */
|
||||
const ValidityDeviceDesc *desc =
|
||||
validity_hal_device_lookup (VALIDITY_DEV_9A);
|
||||
|
||||
g_assert_nonnull (desc);
|
||||
|
||||
ValidityFlashIcParams flash_ic = {
|
||||
.size = 0x200000,
|
||||
.sector_size = 0x1000,
|
||||
.sector_erase_cmd = 0x20,
|
||||
};
|
||||
|
||||
guint8 pub_x[32], pub_y[32];
|
||||
RAND_bytes (pub_x, 32);
|
||||
RAND_bytes (pub_y, 32);
|
||||
|
||||
gsize cmd_len;
|
||||
g_autofree guint8 *cmd =
|
||||
validity_pair_build_partition_flash_cmd (&flash_ic, desc->flash_layout,
|
||||
pub_x, pub_y, &cmd_len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (cmd_len, >, 5);
|
||||
|
||||
/* Command prefix: 0x4f followed by 4 zero bytes */
|
||||
g_assert_cmpuint (cmd[0], ==, 0x4f);
|
||||
g_assert_cmpuint (cmd[1], ==, 0);
|
||||
g_assert_cmpuint (cmd[2], ==, 0);
|
||||
g_assert_cmpuint (cmd[3], ==, 0);
|
||||
g_assert_cmpuint (cmd[4], ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.20: build_tls_flash — produces exactly 4096 bytes
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_tls_flash_size (void)
|
||||
{
|
||||
ValidityPairState state;
|
||||
|
||||
validity_pair_state_init (&state);
|
||||
|
||||
/* Set up minimal test data */
|
||||
guint8 priv_blob[100];
|
||||
guint8 server_cert[200];
|
||||
guint8 ecdh_blob[400];
|
||||
RAND_bytes (priv_blob, sizeof (priv_blob));
|
||||
RAND_bytes (server_cert, sizeof (server_cert));
|
||||
RAND_bytes (ecdh_blob, sizeof (ecdh_blob));
|
||||
|
||||
state.priv_blob = priv_blob;
|
||||
state.priv_blob_len = sizeof (priv_blob);
|
||||
state.server_cert = server_cert;
|
||||
state.server_cert_len = sizeof (server_cert);
|
||||
state.ecdh_blob = ecdh_blob;
|
||||
state.ecdh_blob_len = sizeof (ecdh_blob);
|
||||
|
||||
gsize flash_len;
|
||||
g_autofree guint8 *flash = validity_pair_build_tls_flash (&state, &flash_len);
|
||||
|
||||
g_assert_nonnull (flash);
|
||||
g_assert_cmpuint (flash_len, ==, 0x1000);
|
||||
|
||||
/* Verify padding bytes at end are 0xff */
|
||||
gboolean has_ff_padding = FALSE;
|
||||
for (gsize i = flash_len - 1; i > 0; i--)
|
||||
{
|
||||
if (flash[i] == 0xff)
|
||||
{
|
||||
has_ff_padding = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_true (has_ff_padding);
|
||||
|
||||
/* Don't free embedded pointers since they're stack-allocated */
|
||||
state.priv_blob = NULL;
|
||||
state.server_cert = NULL;
|
||||
state.ecdh_blob = NULL;
|
||||
validity_pair_state_free (&state);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.21: build_tls_flash — block structure
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_tls_flash_blocks (void)
|
||||
{
|
||||
ValidityPairState state;
|
||||
|
||||
validity_pair_state_init (&state);
|
||||
|
||||
guint8 priv_blob[50];
|
||||
guint8 server_cert[100];
|
||||
guint8 ecdh_blob[400];
|
||||
memset (priv_blob, 0xAA, sizeof (priv_blob));
|
||||
memset (server_cert, 0xBB, sizeof (server_cert));
|
||||
memset (ecdh_blob, 0xCC, sizeof (ecdh_blob));
|
||||
|
||||
state.priv_blob = priv_blob;
|
||||
state.priv_blob_len = sizeof (priv_blob);
|
||||
state.server_cert = server_cert;
|
||||
state.server_cert_len = sizeof (server_cert);
|
||||
state.ecdh_blob = ecdh_blob;
|
||||
state.ecdh_blob_len = sizeof (ecdh_blob);
|
||||
|
||||
gsize flash_len;
|
||||
g_autofree guint8 *flash = validity_pair_build_tls_flash (&state, &flash_len);
|
||||
g_assert_nonnull (flash);
|
||||
|
||||
/* Block 0 should be first: id=0, size=1 */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (flash), ==, 0); /* block id */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (flash + 2), ==, 1); /* size = 1 */
|
||||
/* Skip 32-byte hash at flash+4 and 1-byte body at flash+36 */
|
||||
|
||||
/* Next block should be block 4 (priv_blob) at offset 37 */
|
||||
gsize offset = 4 + 32 + 1; /* header(4) + hash(32) + body(1) */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (flash + offset), ==, 4); /* block id */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (flash + offset + 2), ==,
|
||||
sizeof (priv_blob));
|
||||
|
||||
state.priv_blob = NULL;
|
||||
state.server_cert = NULL;
|
||||
state.ecdh_blob = NULL;
|
||||
validity_pair_state_free (&state);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.22: pair state init and free
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_pair_state_lifecycle (void)
|
||||
{
|
||||
ValidityPairState state;
|
||||
|
||||
validity_pair_state_init (&state);
|
||||
|
||||
g_assert_null (state.client_key);
|
||||
g_assert_null (state.server_cert);
|
||||
g_assert_null (state.ecdh_blob);
|
||||
g_assert_null (state.priv_blob);
|
||||
g_assert_cmpuint (state.num_partitions, ==, 0);
|
||||
g_assert_cmpuint (state.erase_step, ==, 0);
|
||||
|
||||
/* Free should be safe on empty state */
|
||||
validity_pair_state_free (&state);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.23: pair state free with allocated resources
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_pair_state_free_with_resources (void)
|
||||
{
|
||||
ValidityPairState state;
|
||||
|
||||
validity_pair_state_init (&state);
|
||||
|
||||
state.server_cert = g_malloc (100);
|
||||
state.server_cert_len = 100;
|
||||
state.ecdh_blob = g_malloc (400);
|
||||
state.ecdh_blob_len = 400;
|
||||
state.priv_blob = g_malloc (161);
|
||||
state.priv_blob_len = 161;
|
||||
|
||||
/* Generate a key to test EVP_PKEY_free path */
|
||||
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id (EVP_PKEY_EC, NULL);
|
||||
EVP_PKEY_keygen_init (pctx);
|
||||
EVP_PKEY_CTX_set_ec_paramgen_curve_nid (pctx, NID_X9_62_prime256v1);
|
||||
EVP_PKEY_keygen (pctx, &state.client_key);
|
||||
EVP_PKEY_CTX_free (pctx);
|
||||
g_assert_nonnull (state.client_key);
|
||||
|
||||
/* Free should release all resources without leak */
|
||||
validity_pair_state_free (&state);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T7.24: encrypt_key — different inputs produce different blobs
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_key_different_inputs (void)
|
||||
{
|
||||
guint8 priv1[32], priv2[32], pub_x[32], pub_y[32];
|
||||
guint8 enc_key[32], val_key[32];
|
||||
|
||||
RAND_bytes (priv1, 32);
|
||||
RAND_bytes (priv2, 32);
|
||||
RAND_bytes (pub_x, 32);
|
||||
RAND_bytes (pub_y, 32);
|
||||
RAND_bytes (enc_key, 32);
|
||||
RAND_bytes (val_key, 32);
|
||||
|
||||
gsize len1, len2;
|
||||
g_autofree guint8 *blob1 = validity_pair_encrypt_key (priv1, pub_x, pub_y,
|
||||
enc_key, val_key, &len1);
|
||||
g_autofree guint8 *blob2 = validity_pair_encrypt_key (priv2, pub_x, pub_y,
|
||||
enc_key, val_key, &len2);
|
||||
|
||||
g_assert_nonnull (blob1);
|
||||
g_assert_nonnull (blob2);
|
||||
g_assert_cmpuint (len1, ==, len2);
|
||||
|
||||
/* Different private keys should produce different ciphertexts */
|
||||
g_assert_true (memcmp (blob1, blob2, len1) != 0);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/pair/parse-flash-info-valid",
|
||||
test_parse_flash_info_valid);
|
||||
g_test_add_func ("/validity/pair/parse-flash-info-needs-pairing",
|
||||
test_parse_flash_info_needs_pairing);
|
||||
g_test_add_func ("/validity/pair/parse-flash-info-too-short",
|
||||
test_parse_flash_info_too_short);
|
||||
g_test_add_func ("/validity/pair/serialize-partition",
|
||||
test_serialize_partition);
|
||||
g_test_add_func ("/validity/pair/make-cert-size",
|
||||
test_make_cert_size);
|
||||
g_test_add_func ("/validity/pair/make-cert-deterministic",
|
||||
test_make_cert_deterministic);
|
||||
g_test_add_func ("/validity/pair/encrypt-key-structure",
|
||||
test_encrypt_key_structure);
|
||||
g_test_add_func ("/validity/pair/encrypt-key-hmac-valid",
|
||||
test_encrypt_key_hmac_valid);
|
||||
g_test_add_func ("/validity/pair/build-partition-flash-cmd",
|
||||
test_build_partition_flash_cmd);
|
||||
g_test_add_func ("/validity/pair/build-tls-flash-size",
|
||||
test_build_tls_flash_size);
|
||||
g_test_add_func ("/validity/pair/build-tls-flash-blocks",
|
||||
test_build_tls_flash_blocks);
|
||||
g_test_add_func ("/validity/pair/state-lifecycle",
|
||||
test_pair_state_lifecycle);
|
||||
g_test_add_func ("/validity/pair/state-free-with-resources",
|
||||
test_pair_state_free_with_resources);
|
||||
g_test_add_func ("/validity/pair/encrypt-key-different-inputs",
|
||||
test_encrypt_key_different_inputs);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,349 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity sensor identification and HAL tables
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fpi-byte-utils.h"
|
||||
|
||||
#include "drivers/validity/validity_sensor.h"
|
||||
|
||||
/* ================================================================
|
||||
* T4.1: test_identify_sensor_parse
|
||||
*
|
||||
* Verify that a valid cmd 0x75 response is parsed correctly into
|
||||
* a ValiditySensorIdent (hw_major + hw_version).
|
||||
*
|
||||
* Wire format (after 2-byte status stripped):
|
||||
* [zeroes:4 LE] [version:2 LE] [major:2 LE]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_sensor_parse (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
|
||||
/* Build synthetic response: zeroes=0, version=0x13, major=0x004a */
|
||||
guint8 data[8];
|
||||
|
||||
FP_WRITE_UINT32_LE (&data[0], 0); /* zeroes */
|
||||
FP_WRITE_UINT16_LE (&data[4], 0x0013); /* version */
|
||||
FP_WRITE_UINT16_LE (&data[6], 0x004a); /* major */
|
||||
|
||||
gboolean ok = validity_sensor_parse_identify (data, sizeof (data), &ident);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_cmpuint (ident.hw_major, ==, 0x004a);
|
||||
g_assert_cmpuint (ident.hw_version, ==, 0x0013);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.2: test_identify_sensor_parse_truncated
|
||||
*
|
||||
* Verify that a response shorter than 8 bytes returns FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_sensor_parse_truncated (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
|
||||
guint8 data[7] = { 0 };
|
||||
|
||||
g_assert_false (validity_sensor_parse_identify (data, sizeof (data), &ident));
|
||||
/* Also test with 0 length */
|
||||
g_assert_false (validity_sensor_parse_identify (data, 0, &ident));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.3: test_device_info_lookup_exact
|
||||
*
|
||||
* Verify that lookup with major=0x004a, version=0x13 returns the
|
||||
* correct DeviceInfo for the ThinkPad T480s sensor.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_exact (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0x004a, 0x13);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->major, ==, 0x004a);
|
||||
g_assert_cmpuint (info->type, ==, 0x00b5);
|
||||
g_assert_cmpuint (info->version, ==, 0x13);
|
||||
g_assert_cmpstr (info->name, ==, "SYN 57K0 FM3297-02");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.4: test_device_info_lookup_another
|
||||
*
|
||||
* Verify that lookup with major=0x0071, version=0x01 returns
|
||||
* the VSI 55E entry (type 0xdb).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_another (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0x0071, 0x01);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->type, ==, 0x00db);
|
||||
g_assert_cmpstr (info->name, ==, "VSI 55E FM72-001");
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.5: test_device_info_lookup_unknown
|
||||
*
|
||||
* Verify that a completely unknown major returns NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_unknown (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
info = validity_device_info_lookup (0xffff, 0x01);
|
||||
|
||||
g_assert_null (info);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.6: test_device_info_lookup_fuzzy
|
||||
*
|
||||
* Verify that when version_mask == 0x00, the entry matches any
|
||||
* version (fuzzy match).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_device_info_lookup_fuzzy (void)
|
||||
{
|
||||
const ValidityDeviceInfo *info;
|
||||
|
||||
/* major=0x0000 entries have version_mask=0x00 → always fuzzy match.
|
||||
* But major=0x0000 needs to match the lookup major. */
|
||||
info = validity_device_info_lookup (0x0000, 0x42);
|
||||
|
||||
/* Should match one of the wildcard entries */
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->major, ==, 0x0000);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.7: test_sensor_type_info_lookup
|
||||
*
|
||||
* Verify lookup of sensor type 0x00b5 returns correct geometry.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00b5);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->sensor_type, ==, 0x00b5);
|
||||
g_assert_cmpuint (info->bytes_per_line, ==, 0x78);
|
||||
g_assert_cmpuint (info->repeat_multiplier, ==, 2);
|
||||
g_assert_cmpuint (info->lines_per_calibration_data, ==, 112);
|
||||
g_assert_cmpuint (info->line_width, ==, 112);
|
||||
g_assert_nonnull (info->calibration_blob);
|
||||
g_assert_cmpuint (info->calibration_blob_len, ==, 112);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.8: test_sensor_type_info_lookup_db
|
||||
*
|
||||
* Verify lookup of sensor type 0x00db (55E) returns correct geometry.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup_db (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00db);
|
||||
|
||||
g_assert_nonnull (info);
|
||||
g_assert_cmpuint (info->bytes_per_line, ==, 0x98);
|
||||
g_assert_cmpuint (info->repeat_multiplier, ==, 1);
|
||||
g_assert_cmpuint (info->lines_per_calibration_data, ==, 144);
|
||||
g_assert_cmpuint (info->line_width, ==, 144);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.9: test_sensor_type_info_lookup_unknown
|
||||
*
|
||||
* Verify that an unknown sensor type returns NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_type_info_lookup_unknown (void)
|
||||
{
|
||||
g_assert_null (validity_sensor_type_info_lookup (0xbeef));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.10: test_factory_bits_cmd_format
|
||||
*
|
||||
* Verify that the factory bits command is built correctly.
|
||||
* Expected: [0x6f] [0x00 0x0e] [0x00 0x00] [0x00 0x00 0x00 0x00]
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_factory_bits_cmd_format (void)
|
||||
{
|
||||
guint8 buf[16];
|
||||
gsize len;
|
||||
|
||||
len = validity_sensor_build_factory_bits_cmd (0x0e00, buf, sizeof (buf));
|
||||
|
||||
g_assert_cmpuint (len, ==, 9);
|
||||
g_assert_cmpuint (buf[0], ==, 0x6f);
|
||||
/* tag = 0x0e00 LE */
|
||||
g_assert_cmpuint (buf[1], ==, 0x00);
|
||||
g_assert_cmpuint (buf[2], ==, 0x0e);
|
||||
/* pad 2 bytes */
|
||||
g_assert_cmpuint (buf[3], ==, 0x00);
|
||||
g_assert_cmpuint (buf[4], ==, 0x00);
|
||||
/* pad 4 bytes */
|
||||
g_assert_cmpuint (buf[5], ==, 0x00);
|
||||
g_assert_cmpuint (buf[6], ==, 0x00);
|
||||
g_assert_cmpuint (buf[7], ==, 0x00);
|
||||
g_assert_cmpuint (buf[8], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.11: test_factory_bits_cmd_buffer_too_small
|
||||
*
|
||||
* Verify that a too-small buffer returns 0.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_factory_bits_cmd_buffer_too_small (void)
|
||||
{
|
||||
guint8 buf[4];
|
||||
gsize len;
|
||||
|
||||
len = validity_sensor_build_factory_bits_cmd (0x0e00, buf, sizeof (buf));
|
||||
|
||||
g_assert_cmpuint (len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.12: test_identify_then_lookup
|
||||
*
|
||||
* End-to-end: parse identify_sensor response → DeviceInfo lookup →
|
||||
* SensorTypeInfo lookup. Simulates the T480s sensor (06cb:009a).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_identify_then_lookup (void)
|
||||
{
|
||||
ValiditySensorIdent ident;
|
||||
const ValidityDeviceInfo *dev_info;
|
||||
const ValiditySensorTypeInfo *type_info;
|
||||
|
||||
/* Simulate cmd 0x75 response for T480s: major=0x004a, version=0x13 */
|
||||
guint8 data[8];
|
||||
|
||||
FP_WRITE_UINT32_LE (&data[0], 0);
|
||||
FP_WRITE_UINT16_LE (&data[4], 0x0013);
|
||||
FP_WRITE_UINT16_LE (&data[6], 0x004a);
|
||||
|
||||
g_assert_true (validity_sensor_parse_identify (data, sizeof (data), &ident));
|
||||
g_assert_cmpuint (ident.hw_major, ==, 0x004a);
|
||||
g_assert_cmpuint (ident.hw_version, ==, 0x0013);
|
||||
|
||||
dev_info = validity_device_info_lookup (ident.hw_major, ident.hw_version);
|
||||
g_assert_nonnull (dev_info);
|
||||
g_assert_cmpuint (dev_info->type, ==, 0x00b5);
|
||||
|
||||
type_info = validity_sensor_type_info_lookup (dev_info->type);
|
||||
g_assert_nonnull (type_info);
|
||||
g_assert_cmpuint (type_info->bytes_per_line, ==, 0x78);
|
||||
g_assert_cmpuint (type_info->line_width, ==, 112);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.13: test_sensor_state_lifecycle
|
||||
*
|
||||
* Verify that init zeros the state and clear frees allocated data.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_sensor_state_lifecycle (void)
|
||||
{
|
||||
ValiditySensorState state;
|
||||
|
||||
validity_sensor_state_init (&state);
|
||||
g_assert_null (state.device_info);
|
||||
g_assert_null (state.type_info);
|
||||
g_assert_null (state.factory_bits);
|
||||
g_assert_cmpuint (state.factory_bits_len, ==, 0);
|
||||
|
||||
/* Simulate storing factory bits */
|
||||
state.factory_bits = g_memdup2 ("\x01\x02\x03", 3);
|
||||
state.factory_bits_len = 3;
|
||||
|
||||
validity_sensor_state_clear (&state);
|
||||
g_assert_null (state.factory_bits);
|
||||
g_assert_cmpuint (state.factory_bits_len, ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* T4.14: test_calibration_blob_present
|
||||
*
|
||||
* Verify that the calibration blob for type 0x00b5 has expected
|
||||
* first and last bytes (from python-validity generated_tables).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_calibration_blob_present (void)
|
||||
{
|
||||
const ValiditySensorTypeInfo *info;
|
||||
|
||||
info = validity_sensor_type_info_lookup (0x00b5);
|
||||
g_assert_nonnull (info);
|
||||
g_assert_nonnull (info->calibration_blob);
|
||||
g_assert_cmpuint (info->calibration_blob_len, ==, 112);
|
||||
|
||||
/* First byte: 0x9b, last byte: 0x06 */
|
||||
g_assert_cmpuint (info->calibration_blob[0], ==, 0x9b);
|
||||
g_assert_cmpuint (info->calibration_blob[111], ==, 0x06);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/sensor/identify/parse",
|
||||
test_identify_sensor_parse);
|
||||
g_test_add_func ("/validity/sensor/identify/truncated",
|
||||
test_identify_sensor_parse_truncated);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_exact",
|
||||
test_device_info_lookup_exact);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_another",
|
||||
test_device_info_lookup_another);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_unknown",
|
||||
test_device_info_lookup_unknown);
|
||||
g_test_add_func ("/validity/sensor/devinfo/lookup_fuzzy",
|
||||
test_device_info_lookup_fuzzy);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup",
|
||||
test_sensor_type_info_lookup);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup_db",
|
||||
test_sensor_type_info_lookup_db);
|
||||
g_test_add_func ("/validity/sensor/typeinfo/lookup_unknown",
|
||||
test_sensor_type_info_lookup_unknown);
|
||||
g_test_add_func ("/validity/sensor/factory_bits/cmd_format",
|
||||
test_factory_bits_cmd_format);
|
||||
g_test_add_func ("/validity/sensor/factory_bits/buffer_too_small",
|
||||
test_factory_bits_cmd_buffer_too_small);
|
||||
g_test_add_func ("/validity/sensor/identify_then_lookup",
|
||||
test_identify_then_lookup);
|
||||
g_test_add_func ("/validity/sensor/state_lifecycle",
|
||||
test_sensor_state_lifecycle);
|
||||
g_test_add_func ("/validity/sensor/calibration_blob_present",
|
||||
test_calibration_blob_present);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,789 +0,0 @@
|
|||
/*
|
||||
* Unit tests for validity TLS session management functions
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "fpi-byte-reader.h"
|
||||
|
||||
/* We include the TLS header and use function declarations directly.
|
||||
* The test links against the driver static lib. */
|
||||
#include "drivers/validity/validity_tls.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF produces deterministic output
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_prf_deterministic (void)
|
||||
{
|
||||
guint8 secret[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
guint8 seed[] = { 0x05, 0x06, 0x07, 0x08 };
|
||||
guint8 output1[48];
|
||||
guint8 output2[48];
|
||||
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output1, sizeof (output1));
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output2, sizeof (output2));
|
||||
|
||||
g_assert_cmpmem (output1, sizeof (output1), output2, sizeof (output2));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF with known TLS 1.2 test vector
|
||||
* ================================================================
|
||||
* RFC 5246 does not define test vectors for SHA-256 PRF directly,
|
||||
* but we verify our implementation against python-validity's output.
|
||||
*/
|
||||
static void
|
||||
test_prf_output_length (void)
|
||||
{
|
||||
guint8 secret[32];
|
||||
guint8 seed[64];
|
||||
guint8 output[0x120]; /* Same as key_block size */
|
||||
|
||||
memset (secret, 0xAB, sizeof (secret));
|
||||
memset (seed, 0xCD, sizeof (seed));
|
||||
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output, sizeof (output));
|
||||
|
||||
/* PRF output should not be all zeros */
|
||||
gboolean all_zero = TRUE;
|
||||
for (gsize i = 0; i < sizeof (output); i++)
|
||||
{
|
||||
if (output[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF with different lengths uses correct number of HMAC iters
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_prf_short_output (void)
|
||||
{
|
||||
guint8 secret[] = { 0x01 };
|
||||
guint8 seed[] = { 0x02 };
|
||||
guint8 output_short[16];
|
||||
guint8 output_long[48];
|
||||
|
||||
validity_tls_prf (secret, 1, seed, 1, output_short, sizeof (output_short));
|
||||
validity_tls_prf (secret, 1, seed, 1, output_long, sizeof (output_long));
|
||||
|
||||
/* First 16 bytes should match */
|
||||
g_assert_cmpmem (output_short, 16, output_long, 16);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Encrypt then decrypt roundtrip
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_decrypt_roundtrip (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
/* Set up encryption/decryption keys (same for roundtrip test) */
|
||||
memset (tls.encryption_key, 0x42, TLS_AES_KEY_SIZE);
|
||||
memset (tls.decryption_key, 0x42, TLS_AES_KEY_SIZE);
|
||||
|
||||
guint8 plaintext[] = "Hello, TLS! This is a test message for encryption.";
|
||||
gsize pt_len = sizeof (plaintext);
|
||||
|
||||
gsize enc_len;
|
||||
guint8 *encrypted = validity_tls_encrypt (&tls, plaintext, pt_len, &enc_len);
|
||||
g_assert_nonnull (encrypted);
|
||||
g_assert_cmpuint (enc_len, >, pt_len); /* IV + padded ciphertext */
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, encrypted, enc_len,
|
||||
&dec_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (decrypted);
|
||||
g_assert_cmpmem (plaintext, pt_len, decrypted, dec_len);
|
||||
|
||||
g_free (encrypted);
|
||||
g_free (decrypted);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Encrypt with block-aligned data
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_block_aligned (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
memset (tls.encryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
memset (tls.decryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
|
||||
/* 16 bytes = exactly one AES block */
|
||||
guint8 plaintext[16];
|
||||
memset (plaintext, 0xAA, 16);
|
||||
|
||||
gsize enc_len;
|
||||
guint8 *encrypted = validity_tls_encrypt (&tls, plaintext, 16, &enc_len);
|
||||
g_assert_nonnull (encrypted);
|
||||
/* Should be IV(16) + 32 bytes (16 data + 16 padding since pad=0x0f*16) */
|
||||
g_assert_cmpuint (enc_len, ==, 16 + 32);
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, encrypted, enc_len,
|
||||
&dec_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (decrypted);
|
||||
g_assert_cmpuint (dec_len, ==, 16);
|
||||
g_assert_cmpmem (plaintext, 16, decrypted, 16);
|
||||
|
||||
g_free (encrypted);
|
||||
g_free (decrypted);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Decrypt with invalid data fails
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_decrypt_invalid (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
memset (tls.decryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
|
||||
/* Too short for IV + block */
|
||||
guint8 short_data[10];
|
||||
memset (short_data, 0, sizeof (short_data));
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, short_data,
|
||||
sizeof (short_data),
|
||||
&dec_len, &error);
|
||||
g_assert_null (decrypted);
|
||||
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PSK derivation runs without crashing
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_psk_derivation (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
validity_tls_derive_psk (&tls);
|
||||
|
||||
/* PSK keys should not be all zeros */
|
||||
gboolean all_zero = TRUE;
|
||||
for (gsize i = 0; i < TLS_AES_KEY_SIZE; i++)
|
||||
{
|
||||
if (tls.psk_encryption_key[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
|
||||
all_zero = TRUE;
|
||||
for (gsize i = 0; i < TLS_AES_KEY_SIZE; i++)
|
||||
{
|
||||
if (tls.psk_validation_key[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PSK derivation is deterministic
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_psk_deterministic (void)
|
||||
{
|
||||
ValidityTlsState tls1, tls2;
|
||||
|
||||
validity_tls_init (&tls1);
|
||||
validity_tls_init (&tls2);
|
||||
|
||||
validity_tls_derive_psk (&tls1);
|
||||
validity_tls_derive_psk (&tls2);
|
||||
|
||||
g_assert_cmpmem (tls1.psk_encryption_key, TLS_AES_KEY_SIZE,
|
||||
tls2.psk_encryption_key, TLS_AES_KEY_SIZE);
|
||||
g_assert_cmpmem (tls1.psk_validation_key, TLS_AES_KEY_SIZE,
|
||||
tls2.psk_validation_key, TLS_AES_KEY_SIZE);
|
||||
|
||||
validity_tls_free (&tls1);
|
||||
validity_tls_free (&tls2);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Flash parse with empty data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_empty (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
guint8 empty_flash[] = { 0xFF, 0xFF, 0x00, 0x00 }; /* end block */
|
||||
|
||||
/* Flash with only end marker → missing keys */
|
||||
gboolean result = validity_tls_parse_flash (&tls, empty_flash,
|
||||
sizeof (empty_flash),
|
||||
&error);
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_false (tls.keys_loaded);
|
||||
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Flash parse with truncated data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_truncated (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
guint8 truncated[] = { 0x03, 0x00, 0xFF, 0x00 }; /* cert block w/ impossibly large size */
|
||||
|
||||
gboolean result = validity_tls_parse_flash (&tls, truncated,
|
||||
sizeof (truncated),
|
||||
&error);
|
||||
/* Should fail due to block size exceeding remaining data */
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Init/free cycle doesn't leak
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_init_free (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
validity_tls_init (&tls);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Build ClientHello produces valid TLS record
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_client_hello (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
gsize out_len;
|
||||
guint8 *hello = validity_tls_build_client_hello (&tls, &out_len);
|
||||
|
||||
g_assert_nonnull (hello);
|
||||
g_assert_cmpuint (out_len, >, 4 + 5); /* prefix(4) + record header(5) minimum */
|
||||
|
||||
/* Check prefix: 0x44 0x00 0x00 0x00 */
|
||||
g_assert_cmpint (hello[0], ==, 0x44);
|
||||
g_assert_cmpint (hello[1], ==, 0x00);
|
||||
g_assert_cmpint (hello[2], ==, 0x00);
|
||||
g_assert_cmpint (hello[3], ==, 0x00);
|
||||
|
||||
/* Check TLS record header */
|
||||
g_assert_cmpint (hello[4], ==, 0x16); /* handshake */
|
||||
g_assert_cmpint (hello[5], ==, 0x03); /* version major */
|
||||
g_assert_cmpint (hello[6], ==, 0x03); /* version minor */
|
||||
|
||||
/* client_random should have been set */
|
||||
gboolean has_random = FALSE;
|
||||
for (gsize i = 0; i < TLS_RANDOM_SIZE; i++)
|
||||
{
|
||||
if (tls.client_random[i] != 0)
|
||||
{
|
||||
has_random = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_true (has_random);
|
||||
|
||||
g_free (hello);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Wrap/unwrap with invalid data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_unwrap_invalid (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
gsize out_len;
|
||||
|
||||
/* Short data → truncated record header */
|
||||
guint8 short_data[] = { 0x17, 0x03 };
|
||||
guint8 *result = validity_tls_unwrap_response (&tls, short_data,
|
||||
sizeof (short_data),
|
||||
&out_len, &error);
|
||||
g_assert_null (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* App data before secure channel */
|
||||
guint8 app_early[] = { 0x17, 0x03, 0x03, 0x00, 0x10,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
|
||||
result = validity_tls_unwrap_response (&tls, app_early,
|
||||
sizeof (app_early),
|
||||
&out_len, &error);
|
||||
g_assert_null (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #1 — Flash parse requires PSK for private key
|
||||
*
|
||||
* Private key block (ID 4) is encrypted with PSK. Calling parse_flash
|
||||
* without first deriving PSK must fail (HMAC mismatch), proving the
|
||||
* ordering dependency. This catches the bug where flash_read SSM
|
||||
* parsed flash data BEFORE PSK derivation had occurred.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_needs_psk (void)
|
||||
{
|
||||
ValidityTlsState tls_with_psk, tls_no_psk;
|
||||
|
||||
validity_tls_init (&tls_with_psk);
|
||||
validity_tls_init (&tls_no_psk);
|
||||
|
||||
/* Derive PSK so we can build a valid encrypted private key block */
|
||||
validity_tls_derive_psk (&tls_with_psk);
|
||||
|
||||
/* Build a realistic flash image with a cert block + encrypted privkey block.
|
||||
* We use a minimal cert (just 16 bytes of dummy data) and a privkey block
|
||||
* that's encrypted with the proper PSK. */
|
||||
|
||||
/* Step 1: Build a cert body */
|
||||
guint8 cert_body[16];
|
||||
memset (cert_body, 0xAA, sizeof (cert_body));
|
||||
|
||||
/* Step 2: Build a private-key body encrypted with PSK */
|
||||
guint8 priv_plaintext[96]; /* d(32) + pad for block alignment */
|
||||
memset (priv_plaintext, 0xBB, sizeof (priv_plaintext));
|
||||
|
||||
/* Encrypt plaintext with PSK encryption key */
|
||||
guint8 iv[TLS_IV_SIZE];
|
||||
memset (iv, 0x11, TLS_IV_SIZE);
|
||||
gsize ct_len = sizeof (priv_plaintext);
|
||||
guint8 *ciphertext = g_malloc (TLS_IV_SIZE + ct_len);
|
||||
memcpy (ciphertext, iv, TLS_IV_SIZE);
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
|
||||
int out_len, final_len;
|
||||
EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc (), NULL,
|
||||
tls_with_psk.psk_encryption_key, iv);
|
||||
EVP_CIPHER_CTX_set_padding (ctx, 0);
|
||||
EVP_EncryptUpdate (ctx, ciphertext + TLS_IV_SIZE, &out_len,
|
||||
priv_plaintext, ct_len);
|
||||
EVP_EncryptFinal_ex (ctx, ciphertext + TLS_IV_SIZE + out_len, &final_len);
|
||||
EVP_CIPHER_CTX_free (ctx);
|
||||
gsize enc_total = TLS_IV_SIZE + ct_len;
|
||||
|
||||
/* HMAC over (iv + ciphertext) with psk_validation_key */
|
||||
guint8 mac[TLS_HMAC_SIZE];
|
||||
unsigned int mac_len;
|
||||
HMAC (EVP_sha256 (),
|
||||
tls_with_psk.psk_validation_key, TLS_AES_KEY_SIZE,
|
||||
ciphertext, enc_total, mac, &mac_len);
|
||||
|
||||
/* Private key block payload: 0x02 || ciphertext || hmac */
|
||||
gsize priv_block_len = 1 + enc_total + TLS_HMAC_SIZE;
|
||||
guint8 *priv_block = g_malloc (priv_block_len);
|
||||
priv_block[0] = 0x02;
|
||||
memcpy (priv_block + 1, ciphertext, enc_total);
|
||||
memcpy (priv_block + 1 + enc_total, mac, TLS_HMAC_SIZE);
|
||||
g_free (ciphertext);
|
||||
|
||||
/* Build flash image: [cert_header][cert_body][priv_header][priv_body][end] */
|
||||
GByteArray *flash = g_byte_array_new ();
|
||||
|
||||
/* Cert block header: id=0x0003, size, sha256 hash */
|
||||
guint8 cert_hdr[TLS_FLASH_BLOCK_HEADER_SIZE];
|
||||
FP_WRITE_UINT16_LE (cert_hdr, TLS_FLASH_BLOCK_CERT);
|
||||
FP_WRITE_UINT16_LE (cert_hdr + 2, sizeof (cert_body));
|
||||
GChecksum *cs = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
gsize hash_len = 32;
|
||||
g_checksum_update (cs, cert_body, sizeof (cert_body));
|
||||
g_checksum_get_digest (cs, cert_hdr + 4, &hash_len);
|
||||
g_checksum_free (cs);
|
||||
g_byte_array_append (flash, cert_hdr, sizeof (cert_hdr));
|
||||
g_byte_array_append (flash, cert_body, sizeof (cert_body));
|
||||
|
||||
/* Priv block header */
|
||||
guint8 priv_hdr[TLS_FLASH_BLOCK_HEADER_SIZE];
|
||||
FP_WRITE_UINT16_LE (priv_hdr, TLS_FLASH_BLOCK_PRIVKEY);
|
||||
FP_WRITE_UINT16_LE (priv_hdr + 2, priv_block_len);
|
||||
cs = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
hash_len = 32;
|
||||
g_checksum_update (cs, priv_block, priv_block_len);
|
||||
g_checksum_get_digest (cs, priv_hdr + 4, &hash_len);
|
||||
g_checksum_free (cs);
|
||||
g_byte_array_append (flash, priv_hdr, sizeof (priv_hdr));
|
||||
g_byte_array_append (flash, priv_block, priv_block_len);
|
||||
|
||||
/* End marker */
|
||||
guint8 end_marker[4] = { 0xFF, 0xFF, 0x00, 0x00 };
|
||||
g_byte_array_append (flash, end_marker, sizeof (end_marker));
|
||||
|
||||
/* TEST: Without PSK, parse_flash must fail on the privkey block */
|
||||
GError *error = NULL;
|
||||
gboolean result = validity_tls_parse_flash (&tls_no_psk,
|
||||
flash->data, flash->len,
|
||||
&error);
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
/* Should fail with HMAC-related error since PSK is all zeros */
|
||||
g_clear_error (&error);
|
||||
|
||||
g_byte_array_free (flash, TRUE);
|
||||
g_free (priv_block);
|
||||
validity_tls_free (&tls_with_psk);
|
||||
validity_tls_free (&tls_no_psk);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #2 — READ_FLASH command format
|
||||
*
|
||||
* The READ_FLASH command must be exactly 13 bytes matching
|
||||
* python-validity: pack('<BBBHLL', 0x40, partition, 1, 0, addr, size).
|
||||
* Old code only sent 10 bytes, missing the access flag and reserved
|
||||
* field. This test verifies the command constant and expected layout.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_cmd_format (void)
|
||||
{
|
||||
/* Verify the command byte */
|
||||
g_assert_cmpint (VCSFW_CMD_READ_FLASH, ==, 0x40);
|
||||
|
||||
/* Build the same command as validity_tls_flash_read_run_state does */
|
||||
guint8 cmd[13];
|
||||
cmd[0] = VCSFW_CMD_READ_FLASH;
|
||||
cmd[1] = 0x01; /* partition */
|
||||
cmd[2] = 0x01; /* access flag */
|
||||
FP_WRITE_UINT16_LE (&cmd[3], 0x0000); /* reserved */
|
||||
FP_WRITE_UINT32_LE (&cmd[5], 0x0000); /* offset */
|
||||
FP_WRITE_UINT32_LE (&cmd[9], 0x1000); /* size */
|
||||
|
||||
/* Verify total size is 13 (not 10 like the old bug) */
|
||||
g_assert_cmpuint (sizeof (cmd), ==, 13);
|
||||
|
||||
/* Verify byte layout matches python-validity's pack('<BBBHLL', ...) */
|
||||
g_assert_cmpint (cmd[0], ==, 0x40); /* command */
|
||||
g_assert_cmpint (cmd[1], ==, 0x01); /* partition */
|
||||
g_assert_cmpint (cmd[2], ==, 0x01); /* access flag (was missing) */
|
||||
g_assert_cmpint (cmd[3], ==, 0x00); /* reserved lo */
|
||||
g_assert_cmpint (cmd[4], ==, 0x00); /* reserved hi */
|
||||
/* offset at bytes 5-8 (LE uint32 = 0) */
|
||||
g_assert_cmpint (cmd[5], ==, 0x00);
|
||||
g_assert_cmpint (cmd[6], ==, 0x00);
|
||||
g_assert_cmpint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpint (cmd[8], ==, 0x00);
|
||||
/* size at bytes 9-12 (LE uint32 = 0x1000) */
|
||||
g_assert_cmpint (cmd[9], ==, 0x00);
|
||||
g_assert_cmpint (cmd[10], ==, 0x10);
|
||||
g_assert_cmpint (cmd[11], ==, 0x00);
|
||||
g_assert_cmpint (cmd[12], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #3 — Flash response has 6-byte header
|
||||
*
|
||||
* After vcsfw_cmd_send strips the 2-byte VCSFW status, the flash
|
||||
* response still contains a 6-byte header: [size:4 LE][unknown:2].
|
||||
* Actual flash data starts at offset 6. Old code passed the raw
|
||||
* response directly to parse_flash(), corrupting the block parsing.
|
||||
* This test verifies:
|
||||
* 1) The 6-byte header is correctly structured (size field matches)
|
||||
* 2) parse_flash works on correctly unwrapped data (offset +6)
|
||||
* 3) The raw response differs from the unwrapped payload, proving
|
||||
* that skipping the header is necessary
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_response_header (void)
|
||||
{
|
||||
/* Build a minimal valid flash image: just an end marker */
|
||||
guint8 flash_data[] = { 0xFF, 0xFF, 0x00, 0x00 };
|
||||
|
||||
/* Wrap it in the response header format: [size:4 LE][unk:2][data] */
|
||||
guint32 data_size = sizeof (flash_data);
|
||||
guint8 response[6 + sizeof (flash_data)];
|
||||
|
||||
FP_WRITE_UINT32_LE (response, data_size);
|
||||
response[4] = 0x00; /* unknown byte 1 */
|
||||
response[5] = 0x00; /* unknown byte 2 */
|
||||
memcpy (response + 6, flash_data, sizeof (flash_data));
|
||||
|
||||
/* Verify the unwrap logic: read size from offset 0, skip 6-byte header */
|
||||
guint32 read_size = FP_READ_UINT32_LE (response);
|
||||
g_assert_cmpuint (read_size, ==, sizeof (flash_data));
|
||||
|
||||
const guint8 *payload = response + 6;
|
||||
gsize payload_len = sizeof (response) - 6;
|
||||
g_assert_cmpuint (payload_len, ==, sizeof (flash_data));
|
||||
|
||||
/* Parsing the correctly unwrapped data should succeed (end marker only = no keys) */
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
GError *error = NULL;
|
||||
gboolean result = validity_tls_parse_flash (&tls, payload, payload_len, &error);
|
||||
/* Expected: fails because no keys, but NOT because of corrupt block headers */
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_str_has_prefix (error->message, "TLS flash: incomplete key data"));
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
/* Verify the bug scenario: passing the raw response (with the 6-byte
|
||||
* header) gives DIFFERENT data to the parser than the correctly unwrapped
|
||||
* payload. The first 4 bytes of the raw response are the LE size field
|
||||
* (0x04 0x00 0x00 0x00), which would be misinterpreted as block_id=0x0004
|
||||
* (PRIVKEY block with size 0). This is a data corruption — the parser
|
||||
* receives wrong input either way, but the key point is that the raw
|
||||
* response and the unwrapped payload are NOT the same buffer content. */
|
||||
g_assert_cmpuint (sizeof (response), !=, payload_len);
|
||||
g_assert_true (memcmp (response, payload, payload_len) != 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #4 — TLS handshake expects raw TLS records
|
||||
*
|
||||
* parse_server_hello expects raw TLS records starting with a content
|
||||
* type byte (0x16 for Handshake). The old code used vcsfw_cmd_send
|
||||
* which strips 2 bytes of VCSFW status, corrupting the TLS record.
|
||||
* This test verifies that:
|
||||
* - A valid TLS Handshake record header is accepted
|
||||
* - Data prefixed with a 2-byte VCSFW status is rejected
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_server_hello_rejects_vcsfw_prefix (void)
|
||||
{
|
||||
/* Build a minimal valid TLS ServerHello record */
|
||||
guint8 server_hello_msg[] = {
|
||||
/* Handshake message: ServerHello (type 0x02) */
|
||||
0x02, /* type: ServerHello */
|
||||
0x00, 0x00, 0x26, /* length: 38 bytes */
|
||||
0x03, 0x03, /* version 1.2 */
|
||||
/* 32 bytes server_random */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
0x00, /* session_id length: 0 */
|
||||
0xC0, 0x05, /* cipher suite: 0xC005 */
|
||||
0x00, /* compression: none */
|
||||
};
|
||||
|
||||
gsize hs_len = sizeof (server_hello_msg);
|
||||
|
||||
/* Wrap in TLS record: content_type(1) + version(2) + length(2) + body */
|
||||
gsize raw_tls_len = 5 + hs_len;
|
||||
guint8 *raw_tls = g_malloc (raw_tls_len);
|
||||
|
||||
raw_tls[0] = TLS_CONTENT_HANDSHAKE; /* 0x16 */
|
||||
raw_tls[1] = TLS_VERSION_MAJOR;
|
||||
raw_tls[2] = TLS_VERSION_MINOR;
|
||||
raw_tls[3] = (hs_len >> 8) & 0xff;
|
||||
raw_tls[4] = hs_len & 0xff;
|
||||
memcpy (raw_tls + 5, server_hello_msg, hs_len);
|
||||
|
||||
/* Test 1: parse_server_hello with raw TLS — should succeed */
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
tls.handshake_hash = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
GError *error = NULL;
|
||||
|
||||
gboolean result = validity_tls_parse_server_hello (&tls, raw_tls,
|
||||
raw_tls_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (result);
|
||||
/* Verify server_random was properly extracted */
|
||||
g_assert_cmpint (tls.server_random[0], ==, 0x01);
|
||||
g_assert_cmpint (tls.server_random[31], ==, 0x20);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
/* Test 2: Prepend a 2-byte VCSFW status (0x0000) — simulates what
|
||||
* vcsfw_cmd_send's cmd_receive_cb would have already STRIPPED.
|
||||
* But if the raw recv path is wrong and doesn't strip, the parser
|
||||
* gets [0x00, 0x00, 0x16, ...] — first byte 0x00 is not a valid
|
||||
* TLS content type, so parsing should behave differently. */
|
||||
gsize prefixed_len = 2 + raw_tls_len;
|
||||
guint8 *prefixed = g_malloc (prefixed_len);
|
||||
prefixed[0] = 0x00; /* VCSFW status lo */
|
||||
prefixed[1] = 0x00; /* VCSFW status hi */
|
||||
memcpy (prefixed + 2, raw_tls, raw_tls_len);
|
||||
|
||||
validity_tls_init (&tls);
|
||||
tls.handshake_hash = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
|
||||
result = validity_tls_parse_server_hello (&tls, prefixed, prefixed_len,
|
||||
&error);
|
||||
/* With the 2-byte prefix, the first "record" starts at byte 0:
|
||||
* content_type=0x00 is NOT TLS_CONTENT_HANDSHAKE (0x16), so the
|
||||
* parser treats it as unknown content and either fails or skips it,
|
||||
* and the server_random will NOT match the expected values. */
|
||||
if (result)
|
||||
{
|
||||
/* Even if parsing didn't error, server_random should be wrong */
|
||||
gboolean random_ok = (tls.server_random[0] == 0x01 &&
|
||||
tls.server_random[31] == 0x20);
|
||||
g_assert_false (random_ok);
|
||||
}
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
g_free (raw_tls);
|
||||
g_free (prefixed);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #5 — Client hello has 0x44 prefix (not VCSFW cmd)
|
||||
*
|
||||
* TLS handshake messages use 0x44000000 as a 4-byte prefix, NOT a
|
||||
* standard VCSFW command byte. This test verifies the prefix and that
|
||||
* the TLS record immediately follows (no VCSFW status expected in
|
||||
* response).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_client_hello_tls_prefix (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
validity_tls_init (&tls);
|
||||
|
||||
gsize out_len;
|
||||
guint8 *hello = validity_tls_build_client_hello (&tls, &out_len);
|
||||
g_assert_nonnull (hello);
|
||||
|
||||
/* Must start with 0x44 0x00 0x00 0x00 (TLS prefix, not VCSFW) */
|
||||
g_assert_cmpint (hello[0], ==, 0x44);
|
||||
g_assert_cmpint (hello[1], ==, 0x00);
|
||||
g_assert_cmpint (hello[2], ==, 0x00);
|
||||
g_assert_cmpint (hello[3], ==, 0x00);
|
||||
|
||||
/* Byte 4 must be TLS Handshake content type (0x16) */
|
||||
g_assert_cmpint (hello[4], ==, TLS_CONTENT_HANDSHAKE);
|
||||
|
||||
/* Bytes 5-6 must be TLS version 1.2 (0x0303) */
|
||||
g_assert_cmpint (hello[5], ==, TLS_VERSION_MAJOR);
|
||||
g_assert_cmpint (hello[6], ==, TLS_VERSION_MINOR);
|
||||
|
||||
/* The prefix (0x44) must NOT equal any VCSFW command byte.
|
||||
* Specifically, 0x44 != VCSFW_CMD_READ_FLASH (0x40) and
|
||||
* is not any known VCSFW command. This proves TLS messages
|
||||
* travel on a separate "channel". */
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_GET_VERSION);
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_READ_FLASH);
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_GET_FW_INFO);
|
||||
|
||||
g_free (hello);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/tls/prf/deterministic", test_prf_deterministic);
|
||||
g_test_add_func ("/validity/tls/prf/output-length", test_prf_output_length);
|
||||
g_test_add_func ("/validity/tls/prf/short-output", test_prf_short_output);
|
||||
g_test_add_func ("/validity/tls/encrypt/roundtrip",
|
||||
test_encrypt_decrypt_roundtrip);
|
||||
g_test_add_func ("/validity/tls/encrypt/block-aligned",
|
||||
test_encrypt_block_aligned);
|
||||
g_test_add_func ("/validity/tls/decrypt/invalid", test_decrypt_invalid);
|
||||
g_test_add_func ("/validity/tls/psk/derivation", test_psk_derivation);
|
||||
g_test_add_func ("/validity/tls/psk/deterministic", test_psk_deterministic);
|
||||
g_test_add_func ("/validity/tls/flash/parse-empty", test_flash_parse_empty);
|
||||
g_test_add_func ("/validity/tls/flash/parse-truncated",
|
||||
test_flash_parse_truncated);
|
||||
g_test_add_func ("/validity/tls/init-free", test_init_free);
|
||||
g_test_add_func ("/validity/tls/client-hello", test_build_client_hello);
|
||||
g_test_add_func ("/validity/tls/unwrap/invalid", test_unwrap_invalid);
|
||||
|
||||
/* Regression tests for hardware-discovered bugs */
|
||||
g_test_add_func ("/validity/tls/regression/flash-parse-needs-psk",
|
||||
test_flash_parse_needs_psk);
|
||||
g_test_add_func ("/validity/tls/regression/flash-cmd-format",
|
||||
test_flash_cmd_format);
|
||||
g_test_add_func ("/validity/tls/regression/flash-response-header",
|
||||
test_flash_response_header);
|
||||
g_test_add_func ("/validity/tls/regression/server-hello-rejects-vcsfw-prefix",
|
||||
test_server_hello_rejects_vcsfw_prefix);
|
||||
g_test_add_func ("/validity/tls/regression/client-hello-tls-prefix",
|
||||
test_client_hello_tls_prefix);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
|
@ -1,556 +0,0 @@
|
|||
/*
|
||||
* Regression tests for validity verify/identify/delete/clear operations.
|
||||
*
|
||||
* These tests cover issues found during the Iteration 6 code audit:
|
||||
* 1. parse_match_result dead while loop — TLV dict not iterated
|
||||
* 2. ENROLL_CREATE_USER NULL user_id — validity_db_build_identity(NULL) crash
|
||||
* 3. Identify always returns first gallery print — subtype not matched
|
||||
* 4. delete_storage_dbid field reused for enrollment — struct field abuse
|
||||
* 5. Delete SSM non-functional — del_record never called
|
||||
* 6. match_finger double allocation — 12 bytes freed then 13 allocated
|
||||
* 7. clear_storage returned NOT_SUPPORTED
|
||||
* 8. Stale TODOs
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "fp-enums.h"
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-byte-utils.h"
|
||||
#include "fp-print.h"
|
||||
#include "test-device-fake.h"
|
||||
|
||||
#include "drivers/validity/validity.h"
|
||||
#include "drivers/validity/validity_db.h"
|
||||
#include "drivers/validity/validity_capture.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* Helper: build a TLV dict entry tag(2LE) | len(2LE) | data[len]
|
||||
* Returns bytes written.
|
||||
* ================================================================ */
|
||||
static gsize
|
||||
build_tlv_entry (guint8 *buf, guint16 tag, const guint8 *data, guint16 len)
|
||||
{
|
||||
FP_WRITE_UINT16_LE (&buf[0], tag);
|
||||
FP_WRITE_UINT16_LE (&buf[2], len);
|
||||
if (len > 0)
|
||||
memcpy (&buf[4], data, len);
|
||||
return 4 + len;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Helper: Build a complete match result payload:
|
||||
* total_len(2LE) | TLV entries...
|
||||
* ================================================================ */
|
||||
static guint8 *
|
||||
build_match_payload (guint32 user_dbid,
|
||||
guint16 subtype,
|
||||
const guint8 *hash,
|
||||
gsize hash_len,
|
||||
gsize *out_len)
|
||||
{
|
||||
/* Max size: 2 (total_len) + 3 entries × (4 header + max data) */
|
||||
guint8 *buf = g_new0 (guint8, 256);
|
||||
gsize pos = 2; /* skip total_len placeholder */
|
||||
|
||||
/* Tag 1: user_dbid (4 bytes LE) */
|
||||
guint8 dbid_data[4];
|
||||
|
||||
FP_WRITE_UINT32_LE (dbid_data, user_dbid);
|
||||
pos += build_tlv_entry (&buf[pos], 1, dbid_data, 4);
|
||||
|
||||
/* Tag 3: subtype (2 bytes LE) */
|
||||
guint8 sub_data[2];
|
||||
FP_WRITE_UINT16_LE (sub_data, subtype);
|
||||
pos += build_tlv_entry (&buf[pos], 3, sub_data, 2);
|
||||
|
||||
/* Tag 4: hash */
|
||||
if (hash && hash_len > 0)
|
||||
pos += build_tlv_entry (&buf[pos], 4, hash, hash_len);
|
||||
|
||||
/* Write total_len at offset 0 */
|
||||
FP_WRITE_UINT16_LE (&buf[0], (guint16) (pos - 2));
|
||||
|
||||
*out_len = pos;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1: parse_match_result with valid TLV data
|
||||
*
|
||||
* Regression: Issue #1 — dead while loop would never extract fields.
|
||||
* Verifies that user_dbid, subtype, and hash are correctly parsed
|
||||
* from a TLV dictionary matching python-validity's parse_dict() format.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_match_result_valid (void)
|
||||
{
|
||||
guint8 hash[] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE };
|
||||
gsize payload_len;
|
||||
g_autofree guint8 *payload = build_match_payload (
|
||||
0x00001234, 3, hash, sizeof (hash), &payload_len);
|
||||
|
||||
ValidityMatchResult result = { 0 };
|
||||
gboolean ok = validity_parse_match_result (payload, payload_len, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_true (result.matched);
|
||||
g_assert_cmpuint (result.user_dbid, ==, 0x00001234);
|
||||
g_assert_cmpuint (result.subtype, ==, 3);
|
||||
g_assert_nonnull (result.hash);
|
||||
g_assert_cmpuint (result.hash_len, ==, sizeof (hash));
|
||||
g_assert_cmpmem (result.hash, result.hash_len, hash, sizeof (hash));
|
||||
|
||||
validity_match_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1b: parse_match_result iterates ALL TLV entries
|
||||
*
|
||||
* Regression: The dead while loop would break after first entry.
|
||||
* Build a dict with tag 3 (subtype) BEFORE tag 1 (user_dbid) to
|
||||
* ensure the parser doesn't stop after the first entry.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_match_result_multi_tags (void)
|
||||
{
|
||||
/* Manually build: total_len(2) | tag3(2+2+2) | tag1(2+2+4) | tag4(2+2+3) */
|
||||
guint8 buf[256];
|
||||
gsize pos = 2;
|
||||
|
||||
/* Tag 3 first: subtype = 7 */
|
||||
guint8 sub[2];
|
||||
|
||||
FP_WRITE_UINT16_LE (sub, 7);
|
||||
pos += build_tlv_entry (&buf[pos], 3, sub, 2);
|
||||
|
||||
/* Tag 1 second: user_dbid = 0xDEADBEEF */
|
||||
guint8 dbid[4];
|
||||
FP_WRITE_UINT32_LE (dbid, 0xDEADBEEF);
|
||||
pos += build_tlv_entry (&buf[pos], 1, dbid, 4);
|
||||
|
||||
/* Tag 4 third: hash = {0x11, 0x22, 0x33} */
|
||||
guint8 hash[] = { 0x11, 0x22, 0x33 };
|
||||
pos += build_tlv_entry (&buf[pos], 4, hash, 3);
|
||||
|
||||
FP_WRITE_UINT16_LE (&buf[0], (guint16) (pos - 2));
|
||||
|
||||
ValidityMatchResult result = { 0 };
|
||||
gboolean ok = validity_parse_match_result (buf, pos, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_true (result.matched);
|
||||
g_assert_cmpuint (result.user_dbid, ==, 0xDEADBEEF);
|
||||
g_assert_cmpuint (result.subtype, ==, 7);
|
||||
g_assert_nonnull (result.hash);
|
||||
g_assert_cmpuint (result.hash_len, ==, 3);
|
||||
g_assert_cmpmem (result.hash, result.hash_len, hash, 3);
|
||||
|
||||
validity_match_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1c: parse_match_result with empty dict (no match)
|
||||
*
|
||||
* Regression: A no-match scenario should return ok=TRUE but matched=FALSE.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_match_result_empty (void)
|
||||
{
|
||||
/* total_len = 0, no TLV entries */
|
||||
guint8 buf[2] = { 0x00, 0x00 };
|
||||
|
||||
ValidityMatchResult result = { 0 };
|
||||
gboolean ok = validity_parse_match_result (buf, sizeof (buf), &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_false (result.matched);
|
||||
g_assert_cmpuint (result.user_dbid, ==, 0);
|
||||
g_assert_cmpuint (result.subtype, ==, 0);
|
||||
g_assert_null (result.hash);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1d: parse_match_result with truncated data
|
||||
*
|
||||
* Ensure graceful handling of malformed/truncated payloads.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_match_result_truncated (void)
|
||||
{
|
||||
/* Only 1 byte — too short for total_len */
|
||||
guint8 buf1[1] = { 0x05 };
|
||||
ValidityMatchResult result = { 0 };
|
||||
|
||||
g_assert_false (validity_parse_match_result (buf1, 1, &result));
|
||||
|
||||
/* total_len says 20 but only 6 bytes follow (partial TLV entry) */
|
||||
guint8 buf2[8];
|
||||
FP_WRITE_UINT16_LE (&buf2[0], 20);
|
||||
FP_WRITE_UINT16_LE (&buf2[2], 1); /* tag = 1 */
|
||||
FP_WRITE_UINT16_LE (&buf2[4], 10); /* len = 10, but only 2 bytes remain */
|
||||
buf2[6] = 0xFF;
|
||||
buf2[7] = 0xFF;
|
||||
|
||||
memset (&result, 0, sizeof (result));
|
||||
gboolean ok = validity_parse_match_result (buf2, sizeof (buf2), &result);
|
||||
/* Should return TRUE (parsing succeeded) but matched=FALSE (incomplete entry) */
|
||||
g_assert_true (ok);
|
||||
g_assert_false (result.matched);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1e: parse_match_result ignores unknown tags
|
||||
*
|
||||
* Unknown tags should be skipped without error.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_parse_match_result_unknown_tags (void)
|
||||
{
|
||||
guint8 buf[256];
|
||||
gsize pos = 2;
|
||||
|
||||
/* Unknown tag 99 with 2 bytes of data */
|
||||
guint8 unk[] = { 0x42, 0x43 };
|
||||
|
||||
pos += build_tlv_entry (&buf[pos], 99, unk, 2);
|
||||
|
||||
/* Tag 1: user_dbid = 0x0042 */
|
||||
guint8 dbid[4];
|
||||
FP_WRITE_UINT32_LE (dbid, 0x0042);
|
||||
pos += build_tlv_entry (&buf[pos], 1, dbid, 4);
|
||||
|
||||
FP_WRITE_UINT16_LE (&buf[0], (guint16) (pos - 2));
|
||||
|
||||
ValidityMatchResult result = { 0 };
|
||||
gboolean ok = validity_parse_match_result (buf, pos, &result);
|
||||
|
||||
g_assert_true (ok);
|
||||
g_assert_true (result.matched);
|
||||
g_assert_cmpuint (result.user_dbid, ==, 0x0042);
|
||||
|
||||
validity_match_result_clear (&result);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R2: validity_db_build_identity rejects NULL
|
||||
*
|
||||
* Regression: Issue #2 — NULL user_id was passed to build_identity
|
||||
* which would then be passed to g_variant_new_string(NULL) → crash.
|
||||
* The guard should return NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_identity_null (void)
|
||||
{
|
||||
gsize len = 999;
|
||||
|
||||
/* The function uses g_return_val_if_fail which emits g_critical.
|
||||
* With G_DEBUG=fatal-warnings the critical would be fatal,
|
||||
* so we must expect the message. */
|
||||
g_test_expect_message ("libfprint-validity",
|
||||
G_LOG_LEVEL_CRITICAL,
|
||||
"*assertion*uuid_str*failed*");
|
||||
|
||||
guint8 *id = validity_db_build_identity (NULL, &len);
|
||||
g_test_assert_expected_messages ();
|
||||
|
||||
g_assert_null (id);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R2b: validity_db_build_identity with valid UUID
|
||||
*
|
||||
* Regression: Ensures UUID → identity bytes works correctly
|
||||
* (complementary to the NULL test above).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_identity_valid_uuid (void)
|
||||
{
|
||||
const gchar *uuid = "12345678-1234-5678-1234-567812345678";
|
||||
gsize len;
|
||||
g_autofree guint8 *id = validity_db_build_identity (uuid, &len);
|
||||
|
||||
g_assert_nonnull (id);
|
||||
g_assert_cmpuint (len, >=, VALIDITY_IDENTITY_MIN_SIZE);
|
||||
|
||||
/* Type should be SID (3) */
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&id[0]), ==, VALIDITY_IDENTITY_TYPE_SID);
|
||||
|
||||
/* Length field should be UUID string length */
|
||||
g_assert_cmpuint (FP_READ_UINT32_LE (&id[4]), ==, strlen (uuid));
|
||||
|
||||
/* UUID payload should be present */
|
||||
g_assert_cmpmem (&id[8], strlen (uuid), uuid, strlen (uuid));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R3: Gallery matching by subtype
|
||||
*
|
||||
* Regression: Issue #3 — identify always returned first gallery print
|
||||
* regardless of actual subtype. Now it should match by finger subtype.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_gallery_match_by_subtype (void)
|
||||
{
|
||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
|
||||
g_autoptr(GPtrArray) gallery = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
/* Create 3 prints with fingers: LEFT_THUMB(1), LEFT_INDEX(2), RIGHT_MIDDLE(8) */
|
||||
FpPrint *p1 = fp_print_new (device);
|
||||
fp_print_set_finger (p1, FP_FINGER_LEFT_THUMB);
|
||||
g_ptr_array_add (gallery, g_object_ref_sink (p1));
|
||||
|
||||
FpPrint *p2 = fp_print_new (device);
|
||||
fp_print_set_finger (p2, FP_FINGER_LEFT_INDEX);
|
||||
g_ptr_array_add (gallery, g_object_ref_sink (p2));
|
||||
|
||||
FpPrint *p3 = fp_print_new (device);
|
||||
fp_print_set_finger (p3, FP_FINGER_RIGHT_MIDDLE);
|
||||
g_ptr_array_add (gallery, g_object_ref_sink (p3));
|
||||
|
||||
/* Subtype 2 = LEFT_INDEX → should match p2, not p1 */
|
||||
guint16 subtype_left_index = validity_finger_to_subtype (FP_FINGER_LEFT_INDEX);
|
||||
FpPrint *match = validity_find_gallery_match (gallery, subtype_left_index);
|
||||
g_assert_true (match == p2);
|
||||
|
||||
/* Subtype 8 = RIGHT_MIDDLE → should match p3 */
|
||||
guint16 subtype_right_middle = validity_finger_to_subtype (FP_FINGER_RIGHT_MIDDLE);
|
||||
match = validity_find_gallery_match (gallery, subtype_right_middle);
|
||||
g_assert_true (match == p3);
|
||||
|
||||
/* Subtype 1 = LEFT_THUMB → should match p1 */
|
||||
guint16 subtype_left_thumb = validity_finger_to_subtype (FP_FINGER_LEFT_THUMB);
|
||||
match = validity_find_gallery_match (gallery, subtype_left_thumb);
|
||||
g_assert_true (match == p1);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R3b: Gallery match falls back to first when subtype doesn't match
|
||||
*
|
||||
* The sensor confirmed a match but the subtype can't be correlated
|
||||
* to any gallery entry — should fall back to first.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_gallery_match_fallback (void)
|
||||
{
|
||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_DEVICE_FAKE, NULL);
|
||||
g_autoptr(GPtrArray) gallery = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
FpPrint *p1 = fp_print_new (device);
|
||||
fp_print_set_finger (p1, FP_FINGER_LEFT_THUMB);
|
||||
g_ptr_array_add (gallery, g_object_ref_sink (p1));
|
||||
|
||||
/* Subtype 9 = RIGHT_RING, not in gallery → should fall back to p1 */
|
||||
guint16 subtype_right_ring = validity_finger_to_subtype (FP_FINGER_RIGHT_RING);
|
||||
FpPrint *match = validity_find_gallery_match (gallery, subtype_right_ring);
|
||||
g_assert_true (match == p1);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R3c: Gallery match with NULL/empty gallery
|
||||
*
|
||||
* Should return NULL when gallery is empty or NULL.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_gallery_match_empty (void)
|
||||
{
|
||||
g_autoptr(GPtrArray) empty = g_ptr_array_new_with_free_func (g_object_unref);
|
||||
|
||||
g_assert_null (validity_find_gallery_match (NULL, 1));
|
||||
g_assert_null (validity_find_gallery_match (empty, 1));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R4: enroll_user_dbid field exists separately from delete_storage_dbid
|
||||
*
|
||||
* Regression: Issue #4 — delete_storage_dbid was abused for enrollment.
|
||||
* This compile-time test verifies both fields exist independently.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_struct_separate_fields (void)
|
||||
{
|
||||
/* Verify both fields exist and are at different offsets */
|
||||
g_assert_cmpuint (
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, enroll_user_dbid), !=,
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, delete_storage_dbid));
|
||||
|
||||
/* Also verify delete_finger_subtype and delete_finger_dbid exist
|
||||
* (needed for the functional delete SSM) */
|
||||
g_assert_cmpuint (
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, delete_finger_subtype), !=,
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, delete_storage_dbid));
|
||||
g_assert_cmpuint (
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, delete_finger_dbid), !=,
|
||||
G_STRUCT_OFFSET (FpiDeviceValidity, delete_storage_dbid));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R5: del_record command format
|
||||
*
|
||||
* Regression: Issue #5 — delete SSM never sent del_record cmd.
|
||||
* Verify cmd 0x48 produces correct format: 0x48 | dbid(2LE).
|
||||
* (This already exists in test-validity-db.c but we double-check
|
||||
* the format critical for delete functionality.)
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_del_record_format (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_del_record (0x4321, &len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 3);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_DEL_RECORD);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[1]), ==, 0x4321);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R6: match_finger command is exactly 13 bytes (single allocation)
|
||||
*
|
||||
* Regression: Issue #6 — build_cmd_match_finger allocated 12 bytes,
|
||||
* freed, then re-allocated 13 bytes. Now single allocation.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_match_finger_size (void)
|
||||
{
|
||||
gsize len;
|
||||
g_autofree guint8 *cmd = validity_db_build_cmd_match_finger (&len);
|
||||
|
||||
g_assert_nonnull (cmd);
|
||||
g_assert_cmpuint (len, ==, 13);
|
||||
g_assert_cmpuint (cmd[0], ==, VCSFW_CMD_MATCH_FINGER);
|
||||
g_assert_cmpuint (cmd[1], ==, 0x02);
|
||||
g_assert_cmpuint (cmd[2], ==, 0xFF);
|
||||
|
||||
/* Verify all 5 uint16_le fields */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[3]), ==, 0); /* stg_id */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[5]), ==, 0); /* usr_id */
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[7]), ==, 1);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[9]), ==, 0);
|
||||
g_assert_cmpuint (FP_READ_UINT16_LE (&cmd[11]), ==, 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R7: Clear storage SSM states exist
|
||||
*
|
||||
* Regression: Issue #7 — clear_storage was a stub returning NOT_SUPPORTED.
|
||||
* Verify the CLEAR_* enum states exist (compile-time regression test).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_clear_storage_states_exist (void)
|
||||
{
|
||||
/* Verify clear SSM states exist and are ordered correctly */
|
||||
g_assert_cmpint (CLEAR_GET_STORAGE, ==, 0);
|
||||
g_assert_cmpint (CLEAR_GET_STORAGE_RECV, ==, 1);
|
||||
g_assert_cmpint (CLEAR_DEL_USER, ==, 2);
|
||||
g_assert_cmpint (CLEAR_DEL_USER_RECV, ==, 3);
|
||||
g_assert_cmpint (CLEAR_DONE, ==, 4);
|
||||
g_assert_cmpint (CLEAR_NUM_STATES, ==, 5);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R7b: Delete SSM states are complete
|
||||
*
|
||||
* Verify the delete SSM has all required states including
|
||||
* DEL_RECORD and DEL_RECORD_RECV (which were previously dead code).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_delete_states_exist (void)
|
||||
{
|
||||
g_assert_cmpint (DELETE_GET_STORAGE, ==, 0);
|
||||
g_assert_cmpint (DELETE_GET_STORAGE_RECV, ==, 1);
|
||||
g_assert_cmpint (DELETE_LOOKUP_USER, ==, 2);
|
||||
g_assert_cmpint (DELETE_LOOKUP_USER_RECV, ==, 3);
|
||||
g_assert_cmpint (DELETE_DEL_RECORD, ==, 4);
|
||||
g_assert_cmpint (DELETE_DEL_RECORD_RECV, ==, 5);
|
||||
g_assert_cmpint (DELETE_DONE, ==, 6);
|
||||
g_assert_cmpint (DELETE_NUM_STATES, ==, 7);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* R1f: match_result_clear frees hash
|
||||
*
|
||||
* Ensure the clear function properly frees the hash allocation.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_match_result_clear (void)
|
||||
{
|
||||
ValidityMatchResult result = { 0 };
|
||||
|
||||
result.matched = TRUE;
|
||||
result.user_dbid = 42;
|
||||
result.subtype = 5;
|
||||
result.hash = g_memdup2 ((guint8[]){0x01, 0x02}, 2);
|
||||
result.hash_len = 2;
|
||||
|
||||
validity_match_result_clear (&result);
|
||||
|
||||
g_assert_false (result.matched);
|
||||
g_assert_cmpuint (result.user_dbid, ==, 0);
|
||||
g_assert_cmpuint (result.subtype, ==, 0);
|
||||
g_assert_null (result.hash);
|
||||
g_assert_cmpuint (result.hash_len, ==, 0);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
/* R1: parse_match_result regression tests (Issue #1: dead while loop) */
|
||||
g_test_add_func ("/validity/verify/parse_match_result_valid",
|
||||
test_parse_match_result_valid);
|
||||
g_test_add_func ("/validity/verify/parse_match_result_multi_tags",
|
||||
test_parse_match_result_multi_tags);
|
||||
g_test_add_func ("/validity/verify/parse_match_result_empty",
|
||||
test_parse_match_result_empty);
|
||||
g_test_add_func ("/validity/verify/parse_match_result_truncated",
|
||||
test_parse_match_result_truncated);
|
||||
g_test_add_func ("/validity/verify/parse_match_result_unknown_tags",
|
||||
test_parse_match_result_unknown_tags);
|
||||
g_test_add_func ("/validity/verify/match_result_clear",
|
||||
test_match_result_clear);
|
||||
|
||||
/* R2: identity builder NULL regression (Issue #2: NULL crash) */
|
||||
g_test_add_func ("/validity/verify/build_identity_null",
|
||||
test_build_identity_null);
|
||||
g_test_add_func ("/validity/verify/build_identity_valid_uuid",
|
||||
test_build_identity_valid_uuid);
|
||||
|
||||
/* R3: gallery matching by subtype (Issue #3: always returned first) */
|
||||
g_test_add_func ("/validity/verify/gallery_match_by_subtype",
|
||||
test_gallery_match_by_subtype);
|
||||
g_test_add_func ("/validity/verify/gallery_match_fallback",
|
||||
test_gallery_match_fallback);
|
||||
g_test_add_func ("/validity/verify/gallery_match_empty",
|
||||
test_gallery_match_empty);
|
||||
|
||||
/* R4: struct field separation (Issue #4: field abuse) */
|
||||
g_test_add_func ("/validity/verify/struct_separate_fields",
|
||||
test_struct_separate_fields);
|
||||
|
||||
/* R5: del_record command format (Issue #5: delete SSM non-functional) */
|
||||
g_test_add_func ("/validity/verify/del_record_format",
|
||||
test_del_record_format);
|
||||
|
||||
/* R6: match_finger single allocation (Issue #6: double alloc) */
|
||||
g_test_add_func ("/validity/verify/match_finger_size",
|
||||
test_match_finger_size);
|
||||
|
||||
/* R7: clear/delete storage SSM states (Issue #7: stub) */
|
||||
g_test_add_func ("/validity/verify/clear_storage_states",
|
||||
test_clear_storage_states_exist);
|
||||
g_test_add_func ("/validity/verify/delete_states",
|
||||
test_delete_states_exist);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
5079
tests/test-validity.c
Normal file
5079
tests/test-validity.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue