validity: add 16 regression tests for Iter6 audit fixes

Adds test-validity-verify.c with 16 unit tests that prevent regression
of all 8 issues found during the Iteration 6 code audit (b05657f):

  R1: parse_match_result TLV parsing (5 tests)
    - valid payload with all fields extracted correctly
    - multi-tag iteration (tag ordering independence)
    - empty dict returns no-match
    - truncated/malformed data handled gracefully
    - unknown tags skipped without error

  R1f: match_result_clear frees hash and zeros struct

  R2: identity builder NULL rejection (2 tests)
    - NULL uuid returns NULL (prevented g_variant_new_string crash)
    - valid UUID produces correct identity bytes

  R3: gallery matching by subtype (3 tests)
    - matches correct print by finger subtype
    - falls back to first entry when subtype not found
    - returns NULL for empty/NULL gallery

  R4: struct field separation — enroll_user_dbid != delete_storage_dbid

  R5: del_record command format — cmd 0x48 with dbid(2LE)

  R6: match_finger single allocation — exactly 13 bytes

  R7: SSM state enums exist (2 tests)
    - CLEAR_* states 0-5
    - DELETE_* states 0-7

To make the tests possible, extracted previously-static functions:
  - parse_match_result → validity_parse_match_result (public)
  - ValidityMatchResult struct moved to validity_db.h
  - validity_match_result_clear added to validity_db.c
  - validity_find_gallery_match helper extracted from verify SSM
This commit is contained in:
Leonardo Francisco 2026-04-06 01:27:54 -04:00 committed by Leonardo
parent f7ce74df1b
commit c120ef0797
6 changed files with 636 additions and 36 deletions

View file

@ -266,3 +266,7 @@ void validity_identify (FpDevice *device);
void validity_list (FpDevice *device);
void validity_delete (FpDevice *device);
void validity_clear_storage (FpDevice *device);
/* Gallery matching helper (validity_verify.c) */
FpPrint *validity_find_gallery_match (GPtrArray *gallery,
guint16 subtype);

View file

@ -54,6 +54,13 @@ validity_user_storage_clear (ValidityUserStorage *storage)
memset (storage, 0, sizeof (*storage));
}
void
validity_match_result_clear (ValidityMatchResult *result)
{
g_clear_pointer (&result->hash, g_free);
memset (result, 0, sizeof (*result));
}
void
validity_user_clear (ValidityUser *user)
{

View file

@ -136,6 +136,24 @@ typedef struct
ValidityRecordChild *children; /* owned array */
} ValidityRecordChildren;
/* ================================================================
* Match result parsed from cmd 0x60 (get_match_result) response
* ================================================================ */
typedef struct
{
gboolean matched; /* TRUE if user_dbid (tag 1) was found */
guint32 user_dbid; /* tag 1: matched user record dbid */
guint16 subtype; /* tag 3: matched finger subtype */
guint8 *hash; /* tag 4: match hash (owned, g_free) */
gsize hash_len;
} ValidityMatchResult;
void validity_match_result_clear (ValidityMatchResult *result);
gboolean validity_parse_match_result (const guint8 *data,
gsize data_len,
ValidityMatchResult *result);
/* ================================================================
* Command builders produce binary TLS command payloads
*

View file

@ -167,20 +167,13 @@ verify_start_interrupt_wait (FpiDeviceValidity *self,
* tag 4 hash (variable length)
* ================================================================ */
typedef struct
{
gboolean matched;
guint32 user_dbid;
guint16 subtype;
guint8 *hash;
gsize hash_len;
} MatchResult;
/* MatchResult is now ValidityMatchResult in validity_db.h */
typedef ValidityMatchResult MatchResult;
static void
match_result_clear (MatchResult *r)
{
g_clear_pointer (&r->hash, g_free);
memset (r, 0, sizeof (*r));
validity_match_result_clear (r);
}
/**
@ -203,10 +196,10 @@ match_result_clear (MatchResult *r)
* Returns: %TRUE if parsing succeeded (result may still be !matched
* if the dict was empty), %FALSE on malformed data.
*/
static gboolean
parse_match_result (const guint8 *data,
gsize data_len,
MatchResult *result)
gboolean
validity_parse_match_result (const guint8 *data,
gsize data_len,
ValidityMatchResult *result)
{
FpiByteReader reader;
guint16 total_len;
@ -276,6 +269,37 @@ parse_match_result (const guint8 *data,
return TRUE;
}
/**
* validity_find_gallery_match:
* @gallery: (element-type FpPrint): array of gallery prints
* @subtype: sensor finger subtype from match result
*
* Find the gallery print whose finger matches the given sensor subtype.
* Falls back to the first gallery entry if no subtype match is found
* (the sensor confirmed a match; we just can't correlate the subtype).
*
* Returns: (nullable): the matching FpPrint, or %NULL if gallery is empty
*/
FpPrint *
validity_find_gallery_match (GPtrArray *gallery,
guint16 subtype)
{
if (!gallery || gallery->len == 0)
return NULL;
gint matched_finger = validity_subtype_to_finger (subtype);
for (guint i = 0; i < gallery->len; i++)
{
FpPrint *candidate = g_ptr_array_index (gallery, i);
if (fp_print_get_finger (candidate) == (FpFinger) matched_finger)
return candidate;
}
/* Fallback: sensor confirmed a match but we can't correlate the subtype */
return g_ptr_array_index (gallery, 0);
}
/* ================================================================
* Verify/Identify SSM
* ================================================================ */
@ -466,7 +490,7 @@ verify_ssm_done (FpiSsm *ssm,
if (self->bulk_data && self->bulk_data_len > 0)
{
if (parse_match_result (self->bulk_data, self->bulk_data_len, &match))
if (validity_parse_match_result (self->bulk_data, self->bulk_data_len, &match))
have_match = match.matched;
}
@ -481,31 +505,12 @@ verify_ssm_done (FpiSsm *ssm,
* the finger subtype. The sensor does the actual 1:N match
* internally; we just need to find which gallery FpPrint
* corresponds to the matched subtype. */
FpPrint *gallery_match = NULL;
GPtrArray *gallery = NULL;
fpi_device_get_identify_data (dev, &gallery);
if (gallery)
{
gint matched_finger = validity_subtype_to_finger (match.subtype);
for (guint i = 0; i < gallery->len; i++)
{
FpPrint *candidate = g_ptr_array_index (gallery, i);
if (fp_print_get_finger (candidate) == (FpFinger) matched_finger)
{
gallery_match = candidate;
break;
}
}
/* If no finger match, fall back to first gallery print —
* the sensor confirmed a match even if we can't correlate
* the subtype to a specific gallery entry. */
if (!gallery_match && gallery->len > 0)
gallery_match = g_ptr_array_index (gallery, 0);
}
FpPrint *gallery_match = validity_find_gallery_match (
gallery, match.subtype);
fpi_device_identify_report (dev, gallery_match, NULL, NULL);
}

View file

@ -397,6 +397,21 @@ if 'validity' in supported_drivers
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,
)
endif
# Run udev rule generator with fatal warnings

View file

@ -0,0 +1,551 @@
/*
* 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 ();
}